Strange behaviour after using the ScaleTransform on WP7 - c#

I have two Image controls on each other and I set the alpha channel of some pixels to zero, from the upper one(this is the colorful). But after I "zoom" (width the ScaleTransform), a "border" will be visible around the pixels that have set. Here is a screenshot:
Here is the code:
<Grid Name="grdPhotos">
<Image Stretch="None" Source="picture_grayscale.jpg" Name="photo1" HorizontalAlignment="Left" VerticalAlignment="Top" />
<Image Stretch="None" Source="picture.jpg" Name="photo2" MouseLeftButtonDown="photo2_MouseLeftButtonDown" HorizontalAlignment="Left" VerticalAlignment="Top" />
</Grid>
private void photo2_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
var photo = photo2.Source as WriteableBitmap; // A WriteableBitmap is created before from the Source BitmapImage
for (int x = 100; x < 200; x++)
{
for (int y = 100; y < 200; y++)
{
int index = Convert.ToInt32(photo.PixelWidth * y + x);
if (index > 0 && index < photo.Pixels.Length)
SetPixelAlphaChannel(ref photo.Pixels[index], 0);
}
}
var transform = new ScaleTransform { ScaleX = 2, ScaleY = 2 };
photo1.RenderTransform = photo2.RenderTransform = transform;
}
public void SetPixelAlphaChannel(ref int pixel, byte value)
{
var color = ColorFromPixel(pixel);
if (color.A == value)
return;
color.A = value;
pixel = ColorToPixel(color);
}
private Color ColorFromPixel(int pixel)
{
var argbBytes = BitConverter.GetBytes(pixel);
return new Color { A = argbBytes[3], R = argbBytes[2], G = argbBytes[1], B = argbBytes[0] };
}
private int ColorToPixel(Color color)
{
var argbBytes = new byte[] { color.B, color.G, color.R, color.A };
return BitConverter.ToInt32(argbBytes, 0);
}
Why is this? Or how can I implement a zoom functionality without this "border"? Thanks a lot.

When you zoom an image, the pixel values will be interpolated, this will result in pixels in the border are you are observing being the result of interpolating your transparent pixels with their non transparent neighbours. Unfortunately you cannot control the interpolation behaviour of render transforms. You are going to have to do this yourself, possibly via WriteableBitmap.

Related

How to draw rectangle on JPG in C# for WPF

[![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>

how to draw a rectangle on a canvas with mouse, but see the rectangle while moving the mouse

i'm new to WPF, learning by migrating an existing winforms app.
i'm drawing rectangles on a Canvas, which contains an Image. the rectangles get the X,Y, and Z information from the imported IntPtr object on the Image, which is then analysed as data.
drawing the rectangles is easy enough with Children.Add() and the data capture is working fine, but i can only see the rectangle once it's drawn, so it's hard to know what area you are going to select. i'd like to see it during the drawing as the mouse moves (like a standard lasso rectangle). in winforms i used the Paint event, how to do similar in WPF? i'm using MouseDown and MouseUp events to capture the start and end points of the rectangle
public void MouseDown(MouseButtonEventArgs e, Canvas evImage)
{
if (e.LeftButton == MouseButtonState.Pressed)
startPos = e.GetPosition(evImage);
evImage.CaptureMouse();
}
public void MouseUp(MouseButtonEventArgs e, Canvas evImage)
{
if (Constants.count < 3)
{
if (e.LeftButton == MouseButtonState.Released)
currentPos = e.GetPosition(evImage);
rec = new System.Windows.Shapes.Rectangle()
{
Stroke = System.Windows.Media.Brushes.LightBlue,
StrokeThickness = 1,
Name = "rec" + Constants.count.ToString(),
};
if (startPos.X < currentPos.X)
rec.Width = currentPos.X - startPos.X;
else
rec.Width = startPos.X - currentPos.X;
if (startPos.Y < currentPos.Y)
rec.Height = currentPos.Y - startPos.Y;
else
rec.Height = startPos.Y - currentPos.Y;
if (rec.Height < 8)
rec.Height = 8;
if (rec.Width < 8)
rec.Width = 8;
if (evImage.Children.Count > Constants.count + 1)
evImage.Children.RemoveAt(Constants.count + 1);
evImage.Children.Insert(Constants.count + 1, rec);
Canvas.SetLeft(rec, startPos.X);
Canvas.SetTop(rec, startPos.Y);
evImage.ReleaseMouseCapture();
SetRectangleData();
Constants.count++;
}
else
{
Constants.count = 0;
RecMove(e, evImage);
}
}
XAML - this is my main learning point, i don't know if using the Image in the Canvas is a problem, or if i need to reference something here like the rectangle?
<Canvas x:Name="evCanvas" MouseUp="evCanvas_MouseUp" MouseDown="evCanvas_MouseDown" Grid.Column="1" Height="550" Width="626" HorizontalAlignment="Left" Margin="10,43,0,0" Grid.Row="1" VerticalAlignment="Top" Grid.ColumnSpan="2" Grid.RowSpan="2">
<Image x:Name="evImage" Height="550" Width="626" MouseMove="evImage_MouseMove" RenderTransformOrigin=".5,.5" />
</Canvas>
Just set Left and Top to the minimum x and y and width to max-x and height max-y.
<Canvas x:Name="canvas" MouseDown="Canvas_MouseDown" MouseMove="Canvas_MouseMove" MouseUp="Canvas_MouseUp" Background="Transparent" />
Then doing as follows:
private Point startPoint;
private Rectangle rect;
private void Canvas_MouseDown(object sender, MouseButtonEventArgs e)
{
startPoint = e.GetPosition(canvas);
rect = new Rectangle
{
Stroke = Brushes.LightBlue,
StrokeThickness = 2
};
Canvas.SetLeft(rect,startPoint.X);
Canvas.SetTop(rect,startPoint.Y);
canvas.Children.Add(rect);
}
private void Canvas_MouseMove(object sender, MouseEventArgs e)
{
if(e.LeftButton == MouseButtonState.Released || rect == null)
return;
var pos = e.GetPosition(canvas);
var x = Math.Min(pos.X, startPoint.X);
var y = Math.Min(pos.Y, startPoint.Y);
var w = Math.Max(pos.X, startPoint.X) - x;
var h = Math.Max(pos.Y, startPoint.Y) - y;
rect.Width = w;
rect.Height = h;
Canvas.SetLeft(rect, x);
Canvas.SetTop(rect, y);
}
private void Canvas_MouseUp(object sender, MouseButtonEventArgs e)
{
rect = null;
}

Windows phone 8.1 crop rectangle

I m making windows phone 8.1 app which have crop function. The problem is that i don't know how to make a graphical rectangle that user can resize and than crop image. Image is located on :
Image x:Name="ImagePreview" HorizontalAlignment="Left" Height="492" Margin="10,10,0,0" Stretch="UniformToFill" VerticalAlignment="Top" Width="380" >
Here is the xaml for croping image(including croping rectangle)
<Grid >
<ScrollViewer>
<Grid >
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Button Content="Pick a image" Click="Button_Click" />
<Canvas Grid.Row="1" Width="380" Height="492" Margin="10,10,0,0" VerticalAlignment="Top">
<Image x:Name="original" ImageOpened="original_ImageOpened" Stretch="Uniform" ManipulationCompleted="Rectangle_ManipulationCompleted" ManipulationDelta="Rectangle_ManipulationDelta" ManipulationStarted="Rectangle_ManipulationStarted" ManipulationMode="All" PointerPressed="rect_PointerPressed" Source="Assets/new_arrivals.png" HorizontalAlignment="Left" Height="492" Width="380" >
</Image>
<Rectangle x:Name="rect" StrokeThickness="1" Stroke="Red">
</Rectangle>
</Canvas>
<Button Grid.Row="2" Name="CropBtn" Content="CropImage" Click="CropBtn_Click" />
<Image Grid.Row="3" Stretch="None" Name="FinalCroppedImage"/>
</Grid>
</ScrollViewer>
</Grid>
code behind
public sealed partial class MainPage : Page
{
bool pointerpressed = false;
WriteableBitmap WB_CapturedImage;//for original image
WriteableBitmap WB_CroppedImage;//for cropped image
Point Point1, Point2;
public MainPage()
{
this.InitializeComponent();
// view = CoreApplication.GetCurrentView();
this.NavigationCacheMode = NavigationCacheMode.Required;
CompositionTarget.Rendering += CompositionTarget_Rendering;
}
void CompositionTarget_Rendering(object sender, object e)
{
rect.SetValue(Canvas.LeftProperty, (Point1.X < Point2.X) ? Point1.X : Point2.X);
rect.SetValue(Canvas.TopProperty, (Point1.Y < Point2.Y) ? Point1.Y : Point2.Y);
rect.Width = (int)Math.Abs(Point2.X - Point1.X);
rect.Height = (int)Math.Abs(Point2.Y - Point1.Y);
}
//To generate croping rectangle
private void Rectangle_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e)
{
Point1 = e.Position;//Set first touchable cordinates as point1
Point2 = Point1;
}
private void Rectangle_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e)
{
var point = Point2;
if (e.Position.X <= original.ActualWidth && e.Position.X >= 0)
point.X = e.Position.X;
if (e.Position.Y <= original.ActualHeight && e.Position.Y >= 0)
point.Y = e.Position.Y;
Point2 = point;
}
private void Rectangle_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
var point = Point2;
// Debug.WriteLine(e.Position.X + "&&&" + original.ActualWidth);
if (e.Position.X <= original.ActualWidth && e.Position.X>=0)
point.X = e.Position.X;
if (e.Position.Y <= original.ActualHeight && e.Position.Y >= 0)
point.Y = e.Position.Y;
Point2 = point;
}
private void rect_PointerPressed(object sender, PointerRoutedEventArgs e)
{
pointerpressed = true;
Point1 = e.GetCurrentPoint(original).Position;//Set first touchable cordinates as point1
Point2 = Point1;
}
private async void CropBtn_Click(object sender, RoutedEventArgs e)
{
BitmapDecoder decoder = null;
BitmapImage bImage = original.Source as BitmapImage;
if (storageFile == null && bImage.UriSource!=null)
{
storageFile = await StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx://"+bImage.UriSource.AbsolutePath));
}
using (IRandomAccessStream streamWithContent = await storageFile.OpenAsync(FileAccessMode.Read))
{
decoder = await BitmapDecoder.CreateAsync(streamWithContent);
BitmapFrame bitmapFrame = await decoder.GetFrameAsync(0);
WB_CapturedImage = new WriteableBitmap((int)bitmapFrame.PixelWidth,
(int)bitmapFrame.PixelHeight);
Size cropsize = new Size(Math.Abs(Point2.X - Point1.X), Math.Abs(Point2.Y - Point1.Y));
double originalImageWidth = WB_CapturedImage.PixelWidth;
double originalImageHeight = WB_CapturedImage.PixelHeight;
// Get the size of the image when it is displayed on the phone
double displayedWidth = original.ActualWidth;
double displayedHeight = original.ActualHeight;
// Calculate the ratio of the original image to the displayed image
double widthRatio = originalImageWidth / displayedWidth;
double heightRatio = originalImageHeight / displayedHeight;
WB_CroppedImage = await GetCroppedBitmapAsync(streamWithContent, Point1, cropsize, widthRatio, heightRatio);
FinalCroppedImage.Source = WB_CroppedImage;
}
}
public static async Task<WriteableBitmap> GetCroppedBitmapAsync(IRandomAccessStream originalImage,
Point startPoint, Size cropSize, double scaleWidth, double scaleheight)
{
if (double.IsNaN(scaleWidth) || double.IsInfinity(scaleWidth))
{
scaleWidth = 1;
}
if (double.IsNaN(scaleheight) || double.IsInfinity(scaleheight))
{
scaleheight = 1;
}
// Convert start point and size to integer.
var startPointX = (uint)Math.Floor(startPoint.X * scaleWidth);
var startPointY = (uint)Math.Floor(startPoint.Y * scaleheight);
var height = (uint)Math.Floor(cropSize.Height * scaleheight);
var width = (uint)Math.Floor(cropSize.Width * scaleWidth);
// Create a decoder from the stream. With the decoder, we can get
// the properties of the image.
var decoder = await BitmapDecoder.CreateAsync(originalImage);
// The scaledSize of original image.
var scaledWidth = (uint)(decoder.PixelWidth);
var scaledHeight = (uint)(decoder.PixelHeight);
// Refine the start point and the size.
if (startPointX + width > scaledWidth)
{
startPointX = scaledWidth - width;
}
if (startPointY + height > scaledHeight)
{
startPointY = scaledHeight - height;
}
// Get the cropped pixels.
var pixels = await GetPixelData(decoder, startPointX, startPointY, width, height,
scaledWidth, scaledHeight);
// Stream the bytes into a WriteableBitmap
var cropBmp = new WriteableBitmap((int)width, (int)height);
var pixStream = cropBmp.PixelBuffer.AsStream();
pixStream.Write(pixels, 0, (int)(width * height * 4));
return cropBmp;
}
private static async Task<byte[]> GetPixelData(BitmapDecoder decoder, uint startPointX, uint startPointY,
uint width, uint height, uint scaledWidth, uint scaledHeight)
{
var transform = new BitmapTransform();
var bounds = new BitmapBounds();
bounds.X = startPointX;
bounds.Y = startPointY;
bounds.Height = height;
bounds.Width = width;
transform.Bounds = bounds;
transform.ScaledWidth = scaledWidth;
transform.ScaledHeight = scaledHeight;
// Get the cropped pixels within the bounds of transform.
var pix = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied,
transform,
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.ColorManageToSRgb);
var pixels = pix.DetachPixelData();
return pixels;
}
}
Microsoft have published a sample Windows 10 application which offer crop capabilities on images. Most of the code can be reused in Windows Phone 8.1.
The control is here:
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Controls/CropControl.cs
And it is used in the following page:
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Views/CropPage.xaml
You also have to check the template declared in (search for controls:CropControl):
https://github.com/Microsoft/Appsample-Photosharing/blob/master/PhotoSharingApp/PhotoSharingApp.Universal/Styles/Styles.xaml

UWP - How to tile a background image?

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

Drawing on Image

I'm able to draw on the image using the below code. But my problem is, it is not a continuous line. It looks somehow broken. See below image for better understanding.
My XAML :
<Grid x:Name="Gridimage1" Grid.Column="0">
<Image Name="image1" HorizontalAlignment="Left" VerticalAlignment="Top" Stretch="Fill" >
</Image>
</Grid>
My c# code :
#region "Drawing on image"
static WriteableBitmap writeableBitmap;
static Image i;
public void DrawingOnImage() // this function will be called after image load
{
if (image1.Source != null)
{
i = new Image();
RenderOptions.SetBitmapScalingMode(image1, BitmapScalingMode.NearestNeighbor);
RenderOptions.SetEdgeMode(image1, EdgeMode.Aliased);
BitmapSource BitmapSrc = new FormatConvertedBitmap(image1.Source as BitmapSource, PixelFormats.Default, null, 0);
//writeableBitmap = new WriteableBitmap((int)image1.ActualWidth, (int)image1.ActualHeight, 96, 96, PixelFormats.Bgr32, null);
writeableBitmap = new WriteableBitmap(BitmapSrc);
image1.Source = writeableBitmap;
//image1.Stretch = Stretch.None;
image1.HorizontalAlignment = HorizontalAlignment.Left;
image1.VerticalAlignment = VerticalAlignment.Top;
i = image1;
image1.MouseMove += new MouseEventHandler(i_MouseMove);
image1.MouseLeftButtonDown +=
new MouseButtonEventHandler(i_MouseLeftButtonDown);
image1.MouseRightButtonDown +=
new MouseButtonEventHandler(i_MouseRightButtonDown);
}
}
static void i_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DrawPixel(e);
}
static void i_MouseMove(object sender, MouseEventArgs e)
{
if (e.LeftButton == MouseButtonState.Pressed)
{
DrawPixel(e);
}
}
static void DrawPixel(MouseEventArgs e)
{
double CR = i.Source.Height / i.ActualHeight;
double RR = i.Source.Width / i.ActualWidth;
int column = (int)(e.GetPosition(i).X * RR) ;
int row = (int)(e.GetPosition(i).Y * CR);
// Reserve the back buffer for updates.
writeableBitmap.Lock();
unsafe
{
// Get a pointer to the back buffer.
int pBackBuffer = (int)writeableBitmap.BackBuffer;
// Fin d the address of the pixel to draw.
pBackBuffer += row * writeableBitmap.BackBufferStride;
pBackBuffer += column * 4;
// Compute the pixel's color.
int color_data = 255 << 16; // R
color_data |= 128 << 8; // G
color_data |= 255 << 0; // B
// Assign the color data to the pixel.
*((int*)pBackBuffer) = color_data;
}
// Specify the area of the bitmap that changed.
writeableBitmap.AddDirtyRect(new Int32Rect(column, row, 1, 1));
// Release the back buffer and make it available for display.
writeableBitmap.Unlock();
}
#endregion
Image output :
You can see the line (pink color) I have drawn. It's not a continuous line. Where am I failing?
Update :
My findings after #loxxy's Inputs.Set oldx ,oldy to zero initialy.
if (oldx == 0 && oldy == 0)
{
writeableBitmap.DrawLineAa(column, row, column, row, SelectedColor);
}
else
{
if (Math.Abs(oldx - column) > 10 || Math.Abs(oldy - row) > 10)
{
writeableBitmap.DrawLineAa(column, row, column, row, SelectedColor);
}
else
{
writeableBitmap.DrawLineAa(column, row, oldx, oldy, SelectedColor);
}
}
oldx = column;
oldy = row;
Since you are drawing it pixel by pixel, the mouse has to update the coordinates at a much faster rate.
And I believe it relates to the DPI of the mouse... And you can do nothing about that.
So instead try drawing a line. So something like this in WPF:
WriteableBitmapExtensions.DrawLine
The mouse events you receive will not return consecutive points. If you move the mouse fast, the points returned will not be next to each other. If you want to draw a line under the mouse cursor, you need to draw a line from the previous point drawn to the current point on every MouseMove or MouseButtonDown callback.

Categories

Resources