Scaling Canvas InkStrokes - c#

I am taking a photo with the camera, using it as the background for an InkCanvas, and letting the user draw overtop.
When the user saves their drawing, I am scaling the drawing to match the size of the image in the background. I can not resize the original image.
However, while the length and position of the strokes are being scaled, the widths are not.
Scrolling through StackOverflow I have found people saying that you also need to set the scale on the InkStroke.DrawingAttributes.PenTipTransform as well as the InkStroke.PointTransform, but when I try to set the value to the scale, the value doesn't change.
Origional Values
New Values
Any information on how I can set this value, or another way to o the same thing would be greatly appreciated.
Thanks
UI Code
<!-- Canvas -->
<Image Grid.Row="1" x:Name="imgImage" Stretch="Uniform" HorizontalAlignment="Center" VerticalAlignment="Center" SizeChanged="imgImage_SizeChanged"/>
<InkCanvas Grid.Row="1" x:Name="inkCanvas" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" PointerExited="inkCanvas_PointerExited" Visibility="Collapsed">
<InkCanvas.RenderTransform>
<ScaleTransform x:Name="inkCanvasScaleTransform" />
</InkCanvas.RenderTransform>
</InkCanvas>
EDIT - More information
The image taken by the camera has the resolution 3264x244, the user can then draw on the image with the canvas, but I can't show the entire image on the screen, so it gets scaled down to fit the size of the canvas. The canvas (in this particular case), has a resolution of 1012x759.
When the image is saved, the location of the draw gets scaled up to match the full size of the image. but the width of the stroke does not.
P.S. the scale transform in the XAML is currently not used.
SAVE CODE
StorageFile inputFile = null;
inputFile = await StorageFile.GetFileFromPathAsync(_image.fullPath);
height = (int)inkCanvas.ActualHeight;
width = (int)inkCanvas.ActualWidth;
double scalex = 0;
double scaley = 0;
double offsety = 0;
double offsetx = 0;
double widthscale = 1;
if (inputFile != null)
{
var prop = await inputFile.Properties.GetImagePropertiesAsync();
offsetx = (double)prop.Width - width;
offsety = (double)prop.Height - height;
scalex = ((double)prop.Width - width) / width;
scaley = ((double)prop.Height - height) / height;
width = (int)prop.Width;
height = (int)prop.Height;
widthscale = 1 + ((scalex * scaley) / 2);
}
var strokes = inkCanvas.InkPresenter.StrokeContainer.GetStrokes();
List<InkStroke> ExpandedStrokes = new List<InkStroke>();
foreach (var stroke in strokes)
{
InkStroke strk = stroke.Clone();
Matrix3x2 scaleMatrix = Matrix3x2.CreateScale(1 + (float)scalex, 1 + (float)scaley);
strk.PointTransform = scaleMatrix;
strk.DrawingAttributes.PenTipTransform = scaleMatrix;
ExpandedStrokes.Add(strk);
}
StorageFolder storageFolder = ApplicationData.Current.LocalFolder;
CanvasDevice device = CanvasDevice.GetSharedDevice();
CanvasRenderTarget renderTarget = new CanvasRenderTarget(device, width, height, 96);
using (var ds = renderTarget.CreateDrawingSession())
{
ds.Clear(Colors.White);
if (inputFile != null)
{
using (CanvasBitmap image = await CanvasBitmap.LoadAsync(device, inputFile.Path, 96))
{
ds.DrawImage(image);
}
}
ds.DrawInk(ExpandedStrokes);
}
using (var fileStream = await inputFile.OpenAsync(FileAccessMode.ReadWrite))
{
await renderTarget.SaveAsync(fileStream, CanvasBitmapFileFormat.Jpeg, 1f);
}
The scale for the PenTip may not be correct, but was just trying to get it making a change.
EXAMPLE IMAGES
Before Scaling (1012x759)
After Scaling (3264x2448)

The InkStroke.PointTransform is for resizing the length of the strokes, it will not scale the strokes's thickness. If you want to display the strokes with corresponding width, you can use the InkCanvas.RenderTransform and set it's ScaleX and ScaleY, But it will not affect the real effect of the Stroke's thickness, it just adjusts the InkCanvas's size to make the Strokes seem to be scaled. There seems no a property to change the real thickness of strokes that have be drawn on the images.
Actually, when you take a photo and put it in the Image control as a background image, then the users can draw on the image, you maybe give the user a choice to select the Pen with different configuration to draw. After users finished drawing, they will save the image with strokes, this should be the effect they want. That is to say, we use the pen to draw on a small image, we should just make sure the stroke on the right position, if the image become larger, the strokes should change automatically, I don't think we should change the stroke scale again.

Related

Using Win2D, how to crop an image to a circle

It seems like this should be simple enough, but I'm really struggling with finding any documentation on how I can do this. I'm simply looking to crop an image to turn a square into a circle.
There is a lot of discussion about it, but I can't seem to find a good example of how to do this using UWP/Win2D.
Here is a bit of code to illustrate the issue I was trying to describe in my comments:
// draw a 10x10 grid of circles
var bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/ice.png"); // hex-shaped image is 250x220 pixels
var brush = new CanvasImageBrush(sender, bitmap);
for (var i = 0; i < 10; i++)
{
for (var j = 0; j < 10; j++)
{
//_drawingSession.FillCircle(new Vector2(i * 50, j * 50), (float)(25), Colors.Blue);
_drawingSession.FillCircle(new Vector2(i * 50, j * 50), (float)(25), brush);
}
}
The image below shows how the brush is being cut from the same x/y coordinates based on the vector where the target circle is to be drawn.
Note: the same effect occurs with FillEllipse().
You can try to use CanvasImageBrush and CanvasDrawingSession.FillEllipse Method achieve it.
private async void canvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/image.jpg"))
{
CanvasImageBrush canvasImageBrush = new CanvasImageBrush(sender, bitmap);
args.DrawingSession.FillEllipse(new System.Numerics.Vector2(100f), 100, 100, canvasImageBrush);
}
}
------------ Update -------------
If you want to cut a circle out of the image source, you can configure the CanvasImageBrush.Transform property to scale the image, then cut the circle and display it on the canvas.
private async void canvas_Draw(Microsoft.Graphics.Canvas.UI.Xaml.CanvasControl sender,
Microsoft.Graphics.Canvas.UI.Xaml.CanvasDrawEventArgs args)
{
using (CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(sender, "Assets/image.jpg"))
{
CanvasImageBrush canvasImageBrush = new CanvasImageBrush(sender, bitmap);
System.Numerics.Vector2 center = new System.Numerics.Vector2((float)(bitmap.Size.Width / 2),
(float)(bitmap.Size.Height / 2));
canvasImageBrush.Transform = System.Numerics.Matrix3x2.CreateScale(0.5F, center);
args.DrawingSession.FillEllipse(center, 160, 160, canvasImageBrush);
}
}
You should change some parameters in my above code to satisfy your requirement, such as the scale in the Matrix3x2.CreateScale method.
Okay, after chatting with one of the fellows on the GitHub Win2D project, I finally have a clear answer on how this works - and it works nothing like I would have expected it to work.
First, the bitmap brush image is by default being positioned at 0,0 on the canvas.
In my case, I wanted to cut a circle from the image and draw it someplace else on the canvas. This requires 2 separate bits of math.
First, you need to position the bitmap's top-left-corner (TLC) to where you want the circle to be drawn. This is done by setting the brush's Transform property. In my example, I'm setting the image TLC to 300/300;
// create the brush
var brush = new CanvasImageBrush(sender, _tiles[1]);
brush.Transform = Matrix3x2.CreateTranslation(300, 300);
Now, to cut/draw the circle using the brush image, I have to describe where the center of the image is to be on the canvas. My image is 250x220.
// 300+250/2, 300+220/2 = 425, 410
_args.DrawingSession.FillCircle(new Vector2(425, 410), (float)(110), brush);
This gives the effect of cutting a circle out of my original bitmap and drawing it on the canvas at the desired location.
Hopefully this is clear enough. I know I certainly struggled to find the answer.

Extract Clip from WPF Canvas

I'm having issues trying to not only clip, but to "extract" a portion of a WPF Canvas. So basically I would like the "Clip" to expand to the full size of the window, or convert the clipped item to separate UI Element for exporting to PNG. I write pseudocode because the real code comes from an Autocad model.
double oPrintWidth=1169;
Canvas c = new Canvas();
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
// Define the path to clip
string thisPathData = "M12233 M222333 M3443" // fake
c.Clip = Geometry.Parse(thisPathData);
At this point I have the same size canvas but everything other than my path is now black. And the path is still in the original position. I need to now make the clip the entire canvas.
I have played with RenderTransform but I'm lost as what to do next, I'm not so good with matrix calculations.
Original Canvas (Collection of UI Elements)
AFTER CLIP
DESIRED RESULT
Ultimately this would be printed but would prefer to keep it in WPF until last minute to retain VECTOR properties for translating to SVG/XPS/ETC
To make a Clip of the entire Canvas and then apply that Clip to the Canvas I recommend you let WPF do it for you be setting the ClipToBounds property:
Canvas c = new Canvas();
c.ClipToBounds = true;
If that doesn't suit your needs, I would look at the Margin, ActualWidth, and ActualHeight properties to determine the clip region. Then create a RectangleGeometry that matches the size of your Canvas.
EDIT in response to your comments.
Well, I've had some time to work at it some more. What I have been able to do is create a clip region, then I transformed the canvas so that the clip region filled the canvas as much as possible. I think this is what you are after...
First of all I needed to measure the clipped region:
Rect bounds = canvas.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
This scaling information is used to make the clipped region fit exactly to the size of the canvas.
Now, we need to apply transformations to the canvas:
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
canvas.RenderTransform = group;
So what is happening here? First of all, the objective is to apply a couple transformations. We need to center the clipped region (translation) and we need to make the clipped region larger (scale). Now, when I say clipped region, I mean the contents of that region. In actuality, we are moving the canvas's rendered output. Moving the region bounds is not what we want to do.
To do this in WPF, we need to add each transformation we want to a child of a TransformGroup.
In this case, we are translating the canvas's output so that its top-left corner is (0, 0) This is necessary because afterwards we will scale the rendered output. So, now, we need to scale the canvas's output so that the image fits as large as it can. To do this, we need to create a ratio that compares the canvas size to the clipped region size.
Here is the formula for scaling the output:
ratio = canvasSize / clippedSize
scaledSize = clippsedSize * ratio
Now, scaling the canvas's output will allow the clipped region to appear as large as possible.
Take a look at the results. Here are images demonstrating the canvas's output before and after the transformations are applied:
Before
After
I figure I might as well give you all the code I used:
Canvas c = new Canvas();
double oPrintWidth=100;
double ratio = .89;
c.Width = oPrintWidth * 2.54;
c.Height = c.Width * ratio;
c.Background = new ImageBrush((ImageSource)FindResource("TestImage")) { Stretch = Stretch.UniformToFill };
// Define the path to clip
string newPath = "M 64,64 L 64,128 128,128, 128,64 Z";
c.Clip = Geometry.Parse(newPath);
Rect bounds = c.Clip.Bounds;
double scaleX = c.Width / (bounds.Right - bounds.Left);
double scaleY = c.Height / (bounds.Bottom - bounds.Top);
TransformGroup group = new TransformGroup();
TranslateTransform move = new TranslateTransform(-bounds.Left, -bounds.Top);
ScaleTransform scale = new ScaleTransform(scaleX, scaleY);
group.Children.Add(move);
group.Children.Add(scale);
c.RenderTransform = group;
MyBorder.Child = c;
And the XAML:
<Window.Resources>
<BitmapImage UriSource="uvtest.jpg" x:Key="TestImage"/>
</Window.Resources>
<Grid Background="Gray">
<Border x:Name="MyBorder" Background="White" BorderBrush="Black" BorderThickness="2" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>

Showing part of image in display area of WPF image control using ScaleTransform and TranslateTransform

I have a WPF Image control already working in my application. Using ScaleTransform and TranslateTransform, the Image control has zooming and panning functionality working very well.
I was wondering if there is any way to display certain rectangle area of the image source in the Image control using ScaleTransform and TranslateTransform. In order to do that, I think I need to get/set rectangle coordinates of the image source in view port of the Image control. But it seems that I can't find any reference on this.
I think CroppedBitmap can help you:
<CroppedBitmap x:Key="croppedImage"
Source="{StaticResource masterImage}" SourceRect="30 20 105 50"/>
Lucky for me, the rectangles have all the same size so I could find easily a fixed scale value for ScaleTrensformation such as 5.0 which will fit each rectangle into the view port. Once that determined, I could come up with following function to calculate values for TranslateTransform in terms of coordinate in the image. Hope it may help people in a similar situation.
public void SetImageCoordinate(double x, double y)
{
TransformGroup transformGroup = (TransformGroup)image.RenderTransform;
ScaleTransform transform = (ScaleTransform)transformGroup.Children[0];
ImageSource imageSource = image.Source;
BitmapImage bitmapImage = (BitmapImage) imageSource ;
//Now since you got the image displayed in Image control. You can easily map the mouse position to the Pixel scale.
var pixelMousePositionX = -(x ) / bitmapImage.PixelWidth * transform.ScaleX * image.ActualWidth;
var pixelMousePositionY = -(y) / bitmapImage.PixelHeight * transform.ScaleY * image.ActualHeight;
//MessageBox.Show("X: " + pixelMousePositionX + "; Y: " + pixelMousePositionY);
var tt = (TranslateTransform)((TransformGroup)image.RenderTransform).Children.First(tr => tr is TranslateTransform);
tt.X = pixelMousePositionX;
tt.Y = pixelMousePositionY;
}

create writeableBitmap from ImageBrush

I am attempting to take a 'snapshot' of a videobrush display on a click event, but for some reason I cannot display this snapshot correctly.
double height = Application.Current.Host.Content.ActualHeight;
//Capture the screen
private void freeze_Click(object sender, EventArgs e)
{
// Takes the preview buffer pixel array from camera and returns
// an array of pixels representing the camera's preview buffer
int[] previewBuffer = GetPreviewBuffer();
//Process the preview image
int[] imageData = PhotofunDataContext.SelectedEffect.Effect.ProcessImage(previewBuffer);
//Copy to WriteableBitmap
//previewWriteableBitmap = new WriteableBitmap((int)camera.PreviewResolution.Width, (int)camera.PreviewResolution.Height);
WriteableBitmap freezeWriteableBitmap = new WriteableBitmap((int)camera.PreviewResolution.Width, (int)camera.PreviewResolution.Height);
imageData.CopyTo(freezeWriteableBitmap.Pixels, 0);
freezeWriteableBitmap.Invalidate();
//Display that on the screen
FreezeImage.Source = freezeWriteableBitmap;
//height of device
FreezeImage.Width = height;
//width to maintain 4:3 aspect ratio
FreezeImage.Height = height * (.75);
FreezeImage.Opacity = 1;
}
}
//Get the preview buffer
private int[] GetPreviewBuffer()
{
int[] pixelData = new int[(int)(camera.PreviewResolution.Width * camera.PreviewResolution.Height)];
//int[] pixelData = new int[(int)width * (int)height];
camera.GetPreviewBufferArgb32(pixelData);
return pixelData;
}
The above code is meant to take the snapshot of the videobrush and display it over the currently active videobrush as a freeze frame.
<Rectangle x:Name="videoRectangle" Width="1" Height="1" HorizontalAlignment="Center">
<Rectangle.Fill>
<VideoBrush x:Name="viewfinderBrush" >
<VideoBrush.RelativeTransform>
<CompositeTransform x:Name="viewfinderTransform" Rotation="90"
CenterX="0.5" CenterY="0.5"/>
</VideoBrush.RelativeTransform>
</VideoBrush>
</Rectangle.Fill>
</Rectangle>
<Image x:Name="FreezeImage" Opacity="0" HorizontalAlignment="Center" Stretch="Fill" Canvas.ZIndex="900">
<Image.Projection>
<PlaneProjection RotationZ="-90"/>
</Image.Projection>
</Image>
The FreezeImage is now overlayed the active videobrush, but the image dimensions do not match the phone dimensions. I would like to fit the FreezeImage, without stretching, to the entire phone screen, in Portrait mode.
I attempt to set FreezeImage.Width = height; and FreezeImage.Height = height * (.75); to maintain the camera's natural 4:3 aspect ratio when the image is sized to the phone screen, but this does not overwrite the previewbuffer and FreezeWriteableBitmap dimensions (640x480).
I also attempting hard coding the width(=1064) and height(=800) dimensions for the previewbuffer and FreezeWriteableBitmap but then the image is not displayed at all(its like the writeableBitmap will not accept any other value other than the camera's dimensions).
After playing around with the camera Initialization, I found that regardless of what I set the resolution to, the writeableBitmaps always default to 640x480 which is where I think the problem must be because it seems that this aspect ratio is what is actualy displayed on the phone screen. How can I fix this implementation so that the resulting freezeImage height matches the phone screen height and the width is adjusted so no stretching is performed?
Note: The height value when setting FreezeImage.Width = height; and FreezeImage.Height = height * (.75); is found by setting double height = Application.Current.Host.Content.ActualHeight; to account for future WP7 devices with other dimensions besides the current standard of 480x800.

Rotating Cursor According to Rotated TextBox

I have a TextBox that I allow my users to rotate. But what I would LOVE for my users is to have their Cursor rotate to the same angle that the TextBox was rotated at. For example, if they rotated the TextBox to 28°, then when the Cursor enters that TextBox the Cursor should also rotate itself to 28°.
You can rotate your cursor using the System.Drawing.Icon class from WinForms in combination with WPF's bitmap rotation ability.
The way to do this is to load the icon, convert it to a BitmapSource, use Image and RenderTargetBitmap to rotate it, convert it back to an Icon, save it, and finally update bytes 2, 10, and 11 that make it a .cur instead of a .ico.
Here's what the code looks like:
public Cursor GetRotatedCursor(byte[] curFileBytes, double rotationAngle)
{
// Load as Bitmap, convert to BitmapSource
var origStream = new MemoryStream(curFileBytes);
var origBitmap = new System.Drawing.Icon(origStream).ToBitmap();
var origSource = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(origBitmap.GetHBitmap());
// Construct rotated image
var image = new Image
{
BitmapSource = origSource,
RenderTransform = new RotateTransform(rotationAngle)
};
// Render rotated image to RenderTargetBitmap
var width = origBitmap.Width;
var height = origBitmap.Height;
var resultSource = new RenderTargetBitmap(width, height, 96, 96, PixelFormats.Pbgra32);
resultSource.Render(image);
// Convert to System.Drawing.Bitmap
var pixels = new int[width*height];
resultSource.CopyPixels(pixels, width, 0);
var resultBitmap = new System.Drawing.Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppPargb);
for(int y=0; y<height; y++)
for(int x=0; x<width; x++)
resultBitmap.SetPixel(x, y, Color.FromArgb(pixels[y*width+x]));
// Save to .ico format
var resultStream = new MemoryStream();
new System.Drawing.Icon(resultBitmap.GetHIcon()).Save(resultStream);
// Convert saved file into .cur format
resultStream.Seek(2); resultStream.WriteByte(curFileBytes, 2, 1);
resultStream.Seek(10); resultStream.WriteByte(curFileBytes, 10, 2);
resultStream.Seek(0);
// Construct Cursor
return new Cursor(resultStream);
}
If you want to avoid the loop, you can replace it with a small bit of usafe code to call the System.Drawing.Bitmap constructor that takes initialization data:
fixed(int* bits = pixels)
{
resultBitmap = new System.Drawing.Bitmap(width, height, width, System.Drawing.Imaging.PixelFormat.Format32bppPargb, new IntPtr(bits));
}
You'll need to call this every time your TextBox rotation changes. This can be done either from the code that rotates your TextBox, or from a PropertyChangedCallback on a value that is bound to the TextBox's rotation.
mmm I'm not sure... but since the cursor is managed by Windows.. I guess you would need to hide the cursor when it enters the textbox and draw your own (which would be easy to rotate since you are rotating the other controls).
Heh, Googling for a way to do this, the first result was naturally from SO, you might wanna check the accepted answer (if you are using wpf):
Custom cursor in WPF?

Categories

Resources