I made an effect called MosaicEffect myself (borrowing code from various places), but in certain situations the mosaic effect does not work, and I am verifying this problem. In order to isolate this problem, I tried it with the existing System.Windows.Media.Effects.BlurEffect instead of my MosaicEffect, but it didn't work either, so I think there's something wrong with my method.
https://i.stack.imgur.com/5Ty1S.png
Minimal source code is here:
https://github.com/dhq-boiler/ForStackOverflow/tree/develop/Question20230124/ShaderEffectSample/ShaderEffectSample
Place the Image object dynamically from the ViewModel (MainWindowViewModel.cs),
I want to apply BlurEffect again, but it doesn't work.
Specifically, it is displayed in its original state without blurring.
(In terms of UI, when the button with Grid.Column=1, Grid.Row=3 is pressed)
https://i.stack.imgur.com/c94jq.jpg
What is missing?...
Part of ViewModels/MainWindowViewModel.cs:
public class MainWindowViewModel : BindableBase
{
public ReactiveCommand BlurEffectImage { get; }
:
public MainWindowViewModel()
{
BlurEffectImage = new ReactiveCommand().WithSubscribe(() =>
{
var mosaic = new MosaicViewModel();
mosaic.Bitmap = new BitmapImage(new System.Uri("../net7.0-windows10.0.22000.0/Assets/Fm2MhTmakAAlBhz.jpg", System.UriKind.Relative));
var drawingVisual = new DrawingVisual();
using (var context = drawingVisual.RenderOpen())
{
var mainwindow = App.Current.MainWindow as MainWindow;
//When I add an Image object from ViewModel, the blur effect doesn't work for some reason
var image = new Image();
image.Source = mosaic.Bitmap;
image.DataContext = mosaic;
image.Width = mosaic.Bitmap.Width;
image.Height = mosaic.Bitmap.Height;
RenderOptions.SetBitmapScalingMode(image, BitmapScalingMode.HighQuality);
mainwindow.test.Content = image;
image.Effect = new BlurEffect()
{
Radius = 30,
KernelType = KernelType.Gaussian,
};
image.UpdateLayout();
BitmapCacheBrush _brush = new BitmapCacheBrush(image);
//When adding an Image object from ViewModel, the code prepared for testing does not apply the blur effect for some reason
//var label = new Label();
//label.Content = "TEST";
//label.FontSize = 12;
//label.Foreground = new SolidColorBrush(Colors.Red);
//label.Background = new SolidColorBrush(Colors.White);
//var _vbrush = new VisualBrush(label);
//_vbrush.Viewport = new Rect(0, 0, 0.25, 0.25);
//_vbrush.TileMode = TileMode.FlipXY;
//var rectangle = new Rectangle();
//rectangle.Fill = _vbrush;
//rectangle.Width = 1000;
//rectangle.Height = 1000;
//var effect = new BlurEffect();
//effect.Radius = 30;
//effect.KernelType = KernelType.Gaussian;
//rectangle.Effect = effect;
//mainwindow.test.Content = rectangle;
//rectangle.UpdateLayout();
//BitmapCacheBrush _brush = new BitmapCacheBrush(rectangle);
context.DrawRectangle(_brush, null, new Rect(0, 0, mosaic.Bitmap.Width, mosaic.Bitmap.Height));
}
OpenCvSharpHelper.ImShow("BlurEffectResult", drawingVisual, (int)mosaic.Bitmap.Width, (int)mosaic.Bitmap.Height);
});
:
}
}
Helpers/OpenCvSharpHelper.cs:
static class OpenCvSharpHelper
{
[Conditional("DEBUG")]
public static void ImShow(string windowTitle, BitmapSource rtb)
{
FormatConvertedBitmap newFormatedBitmapSource = new FormatConvertedBitmap();
newFormatedBitmapSource.BeginInit();
newFormatedBitmapSource.Source = rtb;
newFormatedBitmapSource.DestinationFormat = PixelFormats.Bgr24;
newFormatedBitmapSource.EndInit();
var mat = OpenCvSharp.WpfExtensions.BitmapSourceConverter.ToMat(newFormatedBitmapSource);
OpenCvSharp.Cv2.ImShow(windowTitle, mat);
}
[Conditional("DEBUG")]
public static void ImShow(string windowTitle, System.Windows.Media.Visual visual, int width, int height)
{
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
rtb.Render(visual);
ImShow(windowTitle, rtb);
}
[Conditional("DEBUG")]
public static void ImShow(string windowTitle, VisualBrush brush, int width, int height)
{
var rtb = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
DrawingVisual visual = new DrawingVisual();
using (DrawingContext context = visual.RenderOpen())
{
context.DrawRectangle(brush, null, new System.Windows.Rect(0, 0, width, height));
}
rtb.Render(visual);
ImShow(windowTitle, rtb);
}
}
What I have tried:
As you can see in the image and code, the XAML definition and placing the Image object from the code-behind (MainWindow.cs) worked well.
Below code is working:
Part of Views/MainWindow.xaml:
<Image
Grid.Row="1"
Grid.Column="1"
Source="../Assets/Fm2MhTmakAAlBhz.jpg">
<Image.Effect>
<BlurEffect Radius="30" />
</Image.Effect>
</Image>
<Image
x:Name="baseImage"
Grid.Row="1"
Grid.Column="2"
Source="../Assets/Fm2MhTmakAAlBhz.jpg">
<Image.DataContext>
<viewModels:MosaicViewModel>
<viewModels:MosaicViewModel.Bitmap>
<BitmapImage UriSource="../Assets/Fm2MhTmakAAlBhz.jpg" />
</viewModels:MosaicViewModel.Bitmap>
</viewModels:MosaicViewModel>
</Image.DataContext>
<Image.Effect>
<effects:MosaicEffect
Width="{Binding ElementName=baseImage, Path=ActualWidth, UpdateSourceTrigger=PropertyChanged}"
Height="{Binding ElementName=baseImage, Path=ActualHeight, UpdateSourceTrigger=PropertyChanged}"
Bytecode="{Binding Path=Bytecode.Value, UpdateSourceTrigger=PropertyChanged}"
Cp="10"
Rp="10" />
</Image.Effect>
</Image>
<ContentControl
x:Name="manualBlurEffect"
Grid.Row="2"
Grid.Column="1" />
<ContentControl
x:Name="manualMosaicEffect"
Grid.Row="2"
Grid.Column="2" />
Part of Views/MainWindow.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
{
//Display Blurred image by Code behind
var imageBlurEffect = new Image();
imageBlurEffect.Source = new BitmapImage(new Uri(#"/Assets/Fm2MhTmakAAlBhz.jpg", UriKind.Relative));
this.manualBlurEffect.Content = imageBlurEffect;
var blurEffect = new BlurEffect();
blurEffect.Radius = 30;
imageBlurEffect.Effect = blurEffect;
}
:
}
}
I would expect to see a properly blurred image when I press the button.
It is clear that:
The Image object must be in a panel somewhere in the View (probably the parent element is required).
You have to do image.UpdateLayout() after putting it in the panel.
I apologize for the long question.
Related
[![enter image description here][1]][1]I have folder with a series of jpgs, which are frames of video that have been converted into jpgs. I made some code that iterates through them and displays them.
I am trying to draw a green box on a JPG image from C#. The Hight, Width, XC and YC are in a XML I just access the data there for each frame. I got it to work using a bitmap but then to display it in WPF I have to convert it into a bitmapImage first. The problem is it takes way to long. I want the video to be played at 25 fps. so all the processing needs to happen under 40 ms. Right now it takes anywhere between .01 and .3 seconds to displays the new image.
Here is the code I have so far-
public void UpdateImage(string imageName, int[] boxData)
{
// imageName is the file path the image
Bitmap oldImage = new Bitmap(imageName);
// if objects are detected
if (boxData.Length != 0)
{
// transforms x and y cords to align box better to light
int newXC = boxData[0] - (boxData[2] / 2);
int newYC = boxData[1] - (boxData[3] / 2);
// ensures cords are not negative
if (newXC < 0)
newXC = 0;
if (newYC < 0)
newYC = 0;
// uses the DrawRectangleBorder to draw rectangles
Bitmap newImage = DrawRectangleBorder(oldImage, new Rectangle(new System.Drawing.Point(newXC, newYC), new System.Drawing.Size(boxData[2], boxData[3])), Color.Green);
// converts Bitmap to BitmapImage
using (MemoryStream memory = new MemoryStream())
{
newImage.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
ImportImage.Source = bitmapImage;
}
}
else
{
Bitmap newImage = oldImage;
// converts Bitmap to BitmapImage
using (MemoryStream memory = new MemoryStream())
{
newImage.Save(memory, ImageFormat.Png);
memory.Position = 0;
BitmapImage bitmapImage = new BitmapImage();
bitmapImage.BeginInit();
bitmapImage.StreamSource = memory;
bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
bitmapImage.EndInit();
ImportImage.Source = bitmapImage;
}
}
}
The DrawRectangleBorder Method-
private static Bitmap DrawRectangleBorder(Bitmap image, Rectangle rectangle, Color colour)
{
// makes new blank Bitmap from the old ones width and height
Bitmap newBitmap = new Bitmap(image.Width, image.Height);
// opens up the blank bit
using (Graphics graphics = Graphics.FromImage(newBitmap))
graphics.DrawImage(image, new Rectangle(0, 0, image.Width, image.Height),
new Rectangle(0, 0, image.Width, image.Height), GraphicsUnit.Pixel);
// what actually draws the rectangles
for (Int32 x = rectangle.Location.X; x <= rectangle.Right && x < image.Width; x++)
for (Int32 y = rectangle.Location.Y; y <= rectangle.Bottom && y < image.Height; y++)
if (y == rectangle.Location.Y || y == rectangle.Bottom || x == rectangle.Location.X || x == rectangle.Right)
newBitmap.SetPixel(x, y, colour);
return newBitmap;
}
Here is what one of the pictures look like, they are 640 by 480 resolution-
[1]: https://i.stack.imgur.com/ZiocC.jpg
Any help would be great!
You could simplify your code by using this XAML
<Canvas>
<Image x:Name="ImportImage"/>
<Path x:Name="ObjectBox"
Width="{Binding ActualWidth, ElementName=ImportImage}"
Height="{Binding ActualHeight, ElementName=ImportImage}"
Stretch="None" Stroke="Green" StrokeThickness="1"/>
</Canvas>
with an UpdateImage method like this:
public void UpdateImage(string imagePath, int[] boxData)
{
ImportImage.Source = new BitmapImage(new Uri(imagePath));
var boxes = new GeometryGroup();
for (int i = 0; i <= boxData.Length - 4; i += 4)
{
int width = boxData[i + 2];
int height = boxData[i + 3];
int x = boxData[i] - width / 2;
int y = boxData[i + 1] - height / 2;
boxes.Children.Add(new RectangleGeometry(new Rect(x, y, width, height)));
}
ObjectBox.Data = boxes;
}
here is a rough demo of using a rectangle overlaid in front of an image and controlling its position and size. Sorry, never shared wpf on SO before, but think this code is all you need. I tested the time and it can go well below 40 ms (but I am not updating the image at all, just the rectangle overlay. In case it is needed, I also found this Fast Video Display WPF but ya, did not implement it or test it out.
".cs"
namespace ImageStreamer
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
System.Windows.Threading.DispatcherTimer dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
Stopwatch stopWatch = new Stopwatch();
long lastTime = 0;
public MainWindow()
{
InitializeComponent();
dispatcherTimer.Tick += dispatcherTimer_Tick;
dispatcherTimer.Interval = new TimeSpan(0, 0, 0,0,25);
stopWatch.Start();
}
private void dispatcherTimer_Tick(object sender, EventArgs e)
{
targetRect.Margin = new Thickness(targetRect.Margin.Left+1, targetRect.Margin.Top+1,0,0);
targetRect.Width += 1;
targetRect.Height += 1;
Trace.WriteLine(lastTime - stopWatch.ElapsedMilliseconds);
lastTime = stopWatch.ElapsedMilliseconds;
}
private void Grid_Initialized(object sender, EventArgs e)
{
}
private void Button_Click(object sender, RoutedEventArgs e)
{
dispatcherTimer.Start();
}
}
}
"xaml"
<Window x:Class="ImageStreamer.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ImageStreamer"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Grid Initialized="Grid_Initialized">
<Image x:Name="imgBox" HorizontalAlignment="Left" Height="265" Margin="166,87,0,0" VerticalAlignment="Top" Width="512" Source="/ZiocC.jpg"/>
<Rectangle x:Name="targetRect" HorizontalAlignment="Left" Height="49" Margin="323,228,0,0" Stroke="Red" VerticalAlignment="Top" Width="113" StrokeThickness="5"/>
<Button Content="Button" HorizontalAlignment="Left" Margin="24,43,0,0" VerticalAlignment="Top" Click="Button_Click"/>
</Grid>
</Window>
I have a set of procedural images that I would like to add as billboards to my helix 3D application.
Currently my application looks as following:
public partial class _3DControl
{
HelixViewport3D hVp3D;
public _3DControl()
{
InitializeComponent();
createView();
}
public void createView()
{
hVp3D = new HelixViewport3D();
var lights = new SunLight();
lights.Altitude=40;
lights.Ambient=0.4;
this.Content = hVp3D;
hVp3D.Children.Add(lights);
this.Show();
}
public void UploadBillboard(BitmapImage im, System.Windows.Media.Media3D.Point3D position,double width,double height)
{
//create material
var mat = MaterialHelper.CreateImageMaterial(im, 0);
var bboard = new BillboardVisual3D();
bboard.Material = mat;
//set coordinates
bboard.Position = position;
bboard.Width = width;
bboard.Height = height;
//add the billboard
hVp3D.Children.Add(bboard);
}
However when I call the function to add a billboard:
HelixLinker.GetHelix().UploadBillboard(((Bitmap)e).bitmapToBitmapImage(),
new System.Windows.Media.Media3D.Point3D(0, 0, 0), 100, 100);
Then I see nothing being added, any idea what I' m doing wrong?
I also tried with the RectangleVisual3D class.
public void UploadRect(BitmapImage im, System.Windows.Media.Media3D.Point3D position, double width, double height)
{
var mat = MaterialHelper.CreateImageMaterial(im, 0);
var bboard = new RectangleVisual3D ();
bboard.Material = mat;
bboard.Width = width;
hVp3D.Children.Add(bboard);
}
Which if execuded in the same way results in a (promising) image however in this case the material appears not to be properly set.
Note: I hope that the BillboardVisual3D is the right class, I'm working on something that will allow me to put image "on the floor" so to speak, I want to have flat images that don't have a depth and allow for transparancy.
Why do you set the Opacity to 0? Then you wont see anything.
Try this:
//create material
var mat = MaterialHelper.CreateImageMaterial(im);
See the documentation
Edit:
Sorry, my bad with the string. But this ist working for me:
Code:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
CreateBilboard();
}
private void CreateBilboard()
{
BitmapImage im = new BitmapImage(new System.Uri(#"C:\Users\Public\Pictures\Sample Pictures\Lighthouse.jpg"));
var mat = MaterialHelper.CreateImageMaterial(im, 1);
var bboard = new BillboardVisual3D();
bboard.Material = mat;
var position = new System.Windows.Media.Media3D.Point3D(0, 0, 0);
var width = 100;
var height = 100;
//set coordinates
bboard.Position = position;
bboard.Width = width;
bboard.Height = height;
//add the billboard
viewPort.Children.Add(bboard);
}
}
XAML:
<Window x:Class="BillboardImageMaterialDemo.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:ht="clr-namespace:HelixToolkit.Wpf;assembly=HelixToolkit.Wpf"
Title="BillboardImageMaterialDemo" Height="480" Width="640">
<Grid>
<ht:HelixViewport3D x:Name="viewPort" ZoomExtentsWhenLoaded="True">
<!-- Remember to add some lights -->
<ht:SunLight/>
</ht:HelixViewport3D>
</Grid>
</Window>
Result:
In a Universal Windows app, I am trying to use a background image (from an ImageSource) and tile it across a control.
XAML
<Grid x:Name="gridBackground">
<ContentPresenter />
</Grid>
C#
void UpdateBackground(ImageSource source)
{
// ...
gridBackground.Background = new ImageBrush {
ImageSource = source,
Stretch = Stretch.None
};
}
According to MSDN, ImageBrush inherits from TileBrush. It even says:
Use for an ImageBrush include decorative effects for text, or tiled
backgrounds for controls or layout containers.
I would assume that this should tile the image, if stretching is disabled, but alas, it just draws the image in the middle of the control. I don't see any actual properties to make it tile.
In WPF, there is a TileMode property and ViewPort can be set to specify the dimensions of the tile. But this seems absent under the Universal Platform.
A previous question refers to WinRT (Windows 8), but I'm hoping for a brush based solution, rather than filling a canvas with images.
How do I tile a background image with UWP?
A previous question refers to WinRT (Windows 8), but I'm hoping for a brush based solution, rather than filling a canvas with images.
Currently, there are only two solution for showing background image in Tile mode in UWP app, the first one of which you are aware is filling a canvas.
The second one I'm using is to create a Panel and draw the image on it, this idea is derived from this article
What this method does is that it abuses the fact that we are drawing repeated sets of lines in a rectangular shape. First, it tries to draw a block at the top with the same height as our tile. Then it copies that block down until it reaches the bottom.
I've modified some code and fix some issues:
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);
}
}
Usage:
<Grid x:Name="rootGrid" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<tileCtrl:TiledBackground
BackgroundImage="Assets/avatar1.png"
Width="{Binding ActualWidth, ElementName=rootGrid}" Height="{Binding ActualHeight, ElementName=rootGrid}"/>
</Grid>
Check the solution in Github
All these variants are heavy for GPU. You should make it via Composition API using 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);
I tried 1000000x1000000 size sprite and it worked with no efforts.
Win2d will throw you an exception if your size is bigger than 16386px.
Actually, it is now possible to create a custom brush (with help of Composition API and Win2D) to achieve the tiling effect. Code sample here: UWP TiledBrush
In short, you just subclass the XamlCompositionBrushBase and override it's OnConnected method:
public class TiledBrush : XamlCompositionBrushBase
{
protected override void OnConnected()
{
var surface = LoadedImageSurface.StartLoadFromUri(ImageSourceUri);
var surfaceBrush = Compositor.CreateSurfaceBrush(surface);
surfaceBrush.Stretch = CompositionStretch.None;
var borderEffect = new BorderEffect()
{
Source = new CompositionEffectSourceParameter("source"),
ExtendX = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap,
ExtendY = Microsoft.Graphics.Canvas.CanvasEdgeBehavior.Wrap
};
var borderEffectFactory = Compositor.CreateEffectFactory(borderEffect);
var borderEffectBrush = borderEffectFactory.CreateBrush();
borderEffectBrush.SetSourceParameter("source", surfaceBrush);
}
}
And then use it as expected:
<Grid>
<Grid.Background>
<local:TiledBrush ImageSourceUri="Assets/Texture.jpg" />
</Grid.Background>
</Grid>
See my answer to this question:
You can tile using the Win2D library. They have sample code as well; there's a tiling sample under "effects" (EffectsSample.xaml.cs).
We have the TilesBrush in the Windows Community Toolkit:
<Border BorderBrush="Black" BorderThickness="1" VerticalAlignment="Center" HorizontalAlignment="Center" Width="400" Height="400">
<Border.Background>
<brushes:TilesBrush TextureUri="ms-appx:///Assets/BrushAssets/TileTexture.png"/>
</Border.Background>
</Border>
We also have the TileControl which allows for animations.
Commenting that when using Win2d from C# one must watch out for tmemory leakage. Also there are some subtleties if you want to dynamically change the bitmap resource.
See the answer to this question for one solution to those points: Repeating brush or tile of image in WinUI 3
The "Border" example in the WindowsCompositorSamples also shows how to do this, with rotation and scaling as well.
Link: https://github.com/microsoft/WindowsCompositionSamples/tree/master/SampleGallery/Samples/SDK%2015063/BorderPlayground
The application is running all good but when i'll go to save is like the margin move the picture and the drawing i do move too. What i'm doing wrong or what i need to do to fix that. I think is a geometry problem.
Xaml Code:
<Page
x:Class="DrawingWithMe.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:DrawingWithMe"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Viewbox>
<Grid x:Name="Grid1" Height="768" Width="1366">
<Canvas x:Name="funnyCanvas" Background="White" Margin="162,10,254,42">
<Rectangle x:Name="Rectangle1" Fill="#FFF4F4F5" Stroke="Black"></Rectangle>
<Image x:Name="image" Source="Assets/Test.gif" Stretch="UniformToFill"/>
</Canvas>
</Grid>
</Viewbox>
<Page.BottomAppBar>
<AppBar x:Name="AppBar" Padding="10,0,10,0">
<Grid>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Left">
<Button Name="Save" Content="Save" VerticalAlignment="Top" Click="Save_Click_1" Grid.Column="1"></Button>
<Button Name="Erase" Content="Erase All" VerticalAlignment="Top" Click="Erase_Click_1" Grid.Column="2"></Button>
<Button x:Name="Copytoclipboard" Content="Copy To ClipBoard" VerticalAlignment="Top" Click="Copytoclipboard_Click_1"></Button>
<Button x:Name="Pastefrom" Content="Paste From ClipBoard" VerticalAlignment="Top" Click="Pastefrom_Click_1"></Button>
<Button x:Name="Recognizeword" Content="Recognize" VerticalAlignment="Top" Click="Recognizeword_Click_1"></Button>
</StackPanel>
</Grid>
</AppBar>
</Page.BottomAppBar>
</Page>
C# Code:
public async void TestingBlit()
{
var backgroundBmp = await BitmapFactory.New(1, 1).FromContent(new Uri(BaseUri, #"///Assets/Test.gif"));
//Image foreground
WriteableBitmap foregroundBmp;
using (InMemoryRandomAccessStream a = new InMemoryRandomAccessStream())
{
await _inkManager.SaveAsync(a);
a.Seek(0);
foregroundBmp = await new WriteableBitmap(1,1).FromStream(a);
}
// Combined
backgroundBmp.Blit(new Rect(0, 0, foregroundBmp.PixelWidth, foregroundBmp.PixelHeight), foregroundBmp,new Rect(0, 0, foregroundBmp.PixelWidth, foregroundBmp.PixelHeight), WriteableBitmapExtensions.BlendMode.ColorKeying);
// Save
Windows.Storage.Pickers.FileSavePicker save = new Windows.Storage.Pickers.FileSavePicker();
save.SuggestedStartLocation = Windows.Storage.Pickers.PickerLocationId.Desktop;
save.DefaultFileExtension = ".gif";
save.FileTypeChoices.Add("GIF", new string[] { ".gif" });
StorageFile filesave = await save.PickSaveFileAsync();
Guid encoderId = Windows.Graphics.Imaging.BitmapEncoder.PngEncoderId;
await WinRTXamlToolkit.Imaging.WriteableBitmapSaveExtensions.SaveToFile(backgroundBmp, filesave, encoderId);
//List<InkStroke> tmp = _inkManager.GetStrokes().ToList();
//tmp.RemoveAt(0);
//RenderStroke(tmp.ElementAt(0), Colors.SkyBlue, 10, 1);
SurfaceImageSource surfaceImageSource = new SurfaceImageSource((int)Rectangle1.ActualWidth, (int)Rectangle1.ActualHeight, true);
ImageBrush brush = new ImageBrush();
brush.ImageSource = image.Source;
Rectangle1.Fill = brush;
}
private void RenderStroke(InkStroke stroke, Color color, double width, double opacity = 1)
{
// Each stroke might have more than one segments
var renderingStrokes = stroke.GetRenderingSegments();
//
// Set up the Path to insert the segments
var path = new Windows.UI.Xaml.Shapes.Path();
path.Data = new PathGeometry();
((PathGeometry)path.Data).Figures = new PathFigureCollection();
var pathFigure = new PathFigure();
pathFigure.StartPoint = renderingStrokes.First().Position;
((PathGeometry)path.Data).Figures.Add(pathFigure);
//
// Foreach segment, we add a BezierSegment
foreach (var renderStroke in renderingStrokes)
{
pathFigure.Segments.Add(new BezierSegment()
{
Point1 = renderStroke.BezierControlPoint1,
Point2 = renderStroke.BezierControlPoint2,
Point3 = renderStroke.Position
});
}
// Set the general options (i.e. Width and Color)
path.StrokeThickness = width;
path.Stroke = new SolidColorBrush(color);
// Opacity is used for highlighter
path.Opacity = opacity;
funnyCanvas.Children.Add(path);
}
}
}
You put the content in a Viewbox, which will stretch it. You need to calculate the on-screen coordinates of your rectangle.
Give this a try.
var scalex = MyViewbox.GetScaleChildX();
var scaley = MyViewbox.GetScaleChildY();
SurfaceImageSource surfaceImageSource = new SurfaceImageSource((int)(scalex * Rectangle1.ActualWidth), (int)(scaley * Rectangle1.ActualHeight), true);
public static double GetChildScaleX(this Viewbox viewbox)
{
if (viewbox.Child == null)
throw new InvalidOperationException("Can't tell effective scale of a Viewbox child for a Viewbox with no child.");
var fe = viewbox.Child as FrameworkElement;
if (fe == null)
throw new InvalidOperationException("Can't tell effective scale of a Viewbox child for a Viewbox with a child that is not a FrameworkElement.");
if (fe.ActualWidth == 0)
throw new InvalidOperationException("Can't tell effective scale of a Viewbox child for a Viewbox with a child that is not laid out.");
return viewbox.ActualWidth / fe.ActualWidth;
}
GetChildScaleY is the same, but with Heights (taken from here).
(Make sure you name your Viewbox)
<Viewbox x:Name="MyViewbox">
I'm using http://awesomium.com/ (1.7 RC2) for the webview and http://perspective.codeplex.com/ for the 3d objects.
XAML:
<p:Workshop3D>
<p:Spherical3D ParallelCount="30" DefaultTextureMapping="True">
<p:Spherical3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush TileMode="None" Stretch="Fill"
ImageSource="{Binding Path=WebTexture, ElementName=MyWpfWindow}" />
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</p:Spherical3D.Material>
</p:Spherical3D>
</p:Workshop3D>
Code Behind:
public MainWindow()
{
_webView = WebCore.CreateWebView(800, 600);
_webView.LoadURL(new Uri(#"http://www.youtube.com"));
while (_webView.IsLoading)
WebCore.Update();
var buffer = (BitmapSurface)_webView.Surface;
var bitmap = new WriteableBitmap(800, 600, 96, 96, PixelFormats.Bgra32, null);
buffer.CopyTo(bitmap.BackBuffer, 800, 4, false, false);
WebTexture = bitmap;
InitializeComponent();
}
private ImageSource _webTexture;
public ImageSource WebTexture
{
get
{
return _webTexture;
}
set { _webTexture = value; RaisePropertyChanged("WebTexture"); }
}
For some reason the get accessor for WebTexture isn't even being called, so I'm guessing I'm missing something here (2 hours of google/bing/duckduckgo didn't bring up anything either).