Extract Clip from WPF Canvas - c#

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>

Related

Why aren't the CenterX and CenterY of my ScaleTransform not working the same for my TextBlock as my lines?

I've got a canvas where, among other things, I have many groups of points that I created by making two lines in a cross along with a TextBlock with the points name. When the user zooms in on this canvas, I want the user to be able to scale up or down the points in order to view them easier.
The problem I'm having is specifically the scaling of the textblock. I need both the two lines as well as the textblock to scale to the CanvasCenter (center of the crosshairs the respective point). The two lines scale perfectly towards the center when I set the transforms CenterX and CenterY to the the center but the textblock scales way off, towards the bottom right of the canvas.
Am I misunderstanding what exactly centerX and centerY are?
public void ScaleDown()
{
ScaleTransform lineTransform = new ScaleTransform();
ScaleTransform textTransform = new ScaleTransform();
scale *= 0.90;
lineTransform.ScaleX = scale;
lineTransform.ScaleY = scale;
lineTransform.CenterX = CanvasCenter[0];
lineTransform.CenterY = CanvasCenter[1];
textTransform.ScaleX *= scale;
textTransform.ScaleY *= scale;
textTransform.CenterX = CanvasCenter[0];
textTransform.CenterY = CanvasCenter[1];
NameText.RenderTransform = textTransform;
Line1.RenderTransform = lineTransform;
Line2.RenderTransform = lineTransform;
}
I have tried scaling to the center and I expected it to move closer to that center, but it moved in a completely different direction.

How can you outline an ellipse without using stroke property?

What I really want is a way to have a negative stroke Thickness value on a WPF shape such as an ellipse, so that the stoke outline paints outwards towards LEFT and TOP of Shape, rather than inside of the shape, over writing my text when I make the thinkness of the stroke too thick... I want the radius of my ellipse to stay constant, but the stroke to grow outwards with increased thinkness, and the LEFT, TOP placement of the shape to remain contant with the inner fill staying the same size and not getting covered up by stroke as it is increased in size.
I tried DropShadowEffect, but its kind of too blurry and not well defined enough...and looks kind of messy... really I just want a solid line going around the outside of the shape...
As you can see from attached picture above, I tried to put shadow around two the ellipses using this code below. the problem is that I want it to be a solid color around the outside like a scaletransform of another ellipse of a different color.
var e = new Ellipse();
DropShadowEffect effect = new DropShadowEffect();
effect.Color =Colors.Orange;
effect.Direction = 0;
effect.BlurRadius = 30;
effect.ShadowDepth = 4;
effect.Opacity=0;
e.Effect = effect;
t.Text = string.Format("abc");
t.Measure(new Size(gwin.XStep, gwin.YStep));
t.Arrange(new Rect(t.DesiredSize));
e.StrokeThickness = 2;
e.Stroke = Brushes.Black;
canvas.Children.Add(e);
canvas.Children.Add(t);
Another possible direction towards solving the problem:
<Ellipse RenderTransformOrigin="0.5,0.5">
<Ellipse.RenderTransform>
<TransformGroup>
<ScaleTransform/>
</TransformGroup>
</Ellipse.RenderTransform>
</Ellipse>
Convert to c# code and place one scaletransform ellipse centered inside another scaled transform ellipse of different colors... not sure how to set it up though.
Solution:
Based on suggestion below. I tried creating a grid, setting the width and height of the grid to the size of my ellipse, then adding two ellipses to the grid with different colors and one with a margin set to -10. and it works perfectly ... just need to place the larger ellipse with margin -10 behind the other ellipse when adding it to the grid...here's what it looks like now..
Solution is in here somewhere:
g = new Grid();
e = new Ellipse();
h = new Ellipse();
t = new TextBlock();
t.HorizontalAlignment = HorizontalAlignment.Center;
t.VerticalAlignment = VerticalAlignment.Center;
t.FontWeight = FontWeights.ExtraBold;
g.Children.Add(h);
g.Children.Add(e);
g.Children.Add(t);
gwin.canvas.Children.Add(g);
t.Text = String.Format("{0}.{1}", x, y);
g.Width = gwin.XStep;
g.Height = gwin.YStep;
Canvas.SetLeft (g, gwin.X1 + gwin.XStep*x*2);
Canvas.SetTop (g, gwin.Y1 + gwin.YStep*y*2);
e.StrokeThickness = 2;
e.Stroke = Brushes.Black;
h.Margin = new Thickness(-10);
You can use double ellipses inside a grid overlaying each other like this:
<Grid Width="100" Height="100">
<Ellipse Fill="Black" Margin="-10"/>
<Ellipse Fill="Red" />
</Grid>
The size of this compound is still 100x100 even though the first ellipse is bigger and rendered out of its boundaries.
You may also use a Path and then do this
I think there is something like border. Or you can draw one elipse and then a second one in smaller that has the background color.

Scaling Canvas InkStrokes

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.

Prevent image clipping during rotation Windows Phone

I am using the WriteableBitmapEx extension method to rotate a WriteableBitmap. bi in the code is a WritableBitmap. The RotateFree method rotates the bitmap in any degree and returns a new rotated WritableBitmap. My code:
private void rotate()
{
degree += 1;
var rotate = bi.RotateFree(degree);
ImageControl.Source = rotate;
}
My problem is since the ImageControl size is fixed, it causes the rotated bitmap to be clipped. So what is the best way to prevent this? I guess I am looking for a way to resize the ImageControl during rotation to prevent clipping. Any suggestions?
UPDATE
Based on this useful info Calculate rotated rectangle size from known bounding box coordinates I think I managed to calculate the bounding box width (bx) and height(by) and resize it accordingly during the rotation
double radian = (degree / 180.0) * Math.PI;
double bx = rotate.PixelWidth * Math.Cos(radian) + rotate.PixelHeight * Math.Sin(radian);
double by = rotate.PixelWidth * Math.Sin(radian) + rotate.PixelHeight * Math.Cos(radian);
While it appears that the ImageControl width and height increases/decreases during rotation, the image is still being clipped.
UPDATE 2
Based on #Rene suggestion, I managed to prevent the clipping. Combined with the ImageControl Width/Height calculation, the image size is retained during rotation by also setting its stretch property to NONE.
The issue now is to make sure the ImageControl resize from its center so that it does not appear moving. I can include a sample project if anyone interested
UPDATE 3
For those who might be interested the final solution. This is how I do it. The result is, the image is rotated without clipping and its size is retained during rotation. In addition, the rotation appears to originate from the center.
To adjust the ImageControl position as it's resizing so that the rotation appears to originated from center, I use this code.
var translationDelta = new Point((ImageControl.ActualWidth - bx) / 2.0, (ImageControl.ActualHeight - by) / 2.0);
UpdateImagePosition(translationDelta);
ApplyPosition();
// This code update the ImageControl position on the canvas
public void UpdateImagePosition(Point delta)
{
var newPosition = new Point(ImagePosition.X + delta.X, ImagePosition.Y + delta.Y);
ImagePosition = newPosition;
}
//This apply the new position to make the ImageControl rotate from center
public void ApplyPosition()
{
ObjComposite.TranslateX = ImagePosition.X;
ObjComposite.TranslateY = ImagePosition.Y;
}
Use RotateFree with the crop parameter set to false: RotateFree(degree, false). Also set the ImageControl Stretch property to Uniform: Stretch="Uniform".
rene

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;
}

Categories

Resources