I have complex StreamGeometry and I want to clip it. Unfortunately, it looks like StreamGeometry doesn't supports combine.
Here is a test.
Xaml:
<Path x:Name="path" Stroke="Red"/>
Code:
var clip = new RectangleGeometry(new Rect(50, 50, 10, 10));
var rect = new RectangleGeometry(new Rect(0, 0, 100, 100));
var stream = new StreamGeometry();
using (var geometry = stream.Open())
{
geometry.BeginFigure(new Point(0, 0), false, true);
geometry.LineTo(new Point(0, 100), true, false);
geometry.LineTo(new Point(100, 100), true, false);
geometry.LineTo(new Point(100, 0), true, false);
}
//path.Data = rect;
//path.Data = stream;
//path.Data = clip;
//path.Data = Geometry.Combine(rect, clip, GeometryCombineMode.Intersect, null);
//path.Data = Geometry.Combine(stream, clip, GeometryCombineMode.Intersect, null);
Uncommenting first line (to show rect) or second line (to show stream) produces same visual result:
Uncommenting third line (to show clip) or fourth line (to show intersection of clip and rect) will produce:
While uncommenting last line (to show intersection of clip and geometry) produce blank screen.
My question is: how to combine (clip) StreamGeometry?
I know there is UIElement.Clip, but:
// blank screen
path.Data = stream;
path.Clip = clip;
// blank screen as well
path.Data = stream;
path.Clip = clip;
You can definitely combine StreamGeometry objects. For this, you want to use a CombinedGeometry object. Here's a simple extension method for drawing polygons with holes:
public static void DrawPolygon(this DrawingContext context, Brush brush, Pen pen, IEnumerable<Point> exteriorRing, IEnumerable<IEnumerable<Point>> interiorRings = null)
{
StreamGeometry geo = new StreamGeometry();
Geometry finalGeometry = geo;
using (StreamGeometryContext ctxExterior = geo.Open())
{
ctxExterior.BeginFigure(exteriorRing.First(), (brush != null), true);
ctxExterior.PolyLineTo(exteriorRing.Skip(1).ToArray(), (pen != null), false);
if (interiorRings != null)
{
foreach (var ring in interiorRings)
{
var interiorGeometry = new StreamGeometry();
using (var ctxInterior = interiorGeometry.Open())
{
ctxInterior.BeginFigure(ring.First(), true, true);
ctxInterior.PolyLineTo(ring.Skip(1).ToArray(), (pen != null), false);
}
finalGeometry = new CombinedGeometry(GeometryCombineMode.Exclude, finalGeometry, interiorGeometry);
}
}
}
context.DrawGeometry(brush, pen, finalGeometry);
}
Solution is simple: do not use StreamGeometry.
To example, this will work (using PathGeometry instead):
var clip = new RectangleGeometry(new Rect(50, 50, 10, 10));
var geometry = new PathGeometry(new[] { new PathFigure(new Point(0, 0), new[] {
new LineSegment(new Point(0, 100), true),
new LineSegment(new Point(100, 100), true),
new LineSegment(new Point(100, 0), true),
}, true) });
path.Data = Geometry.Combine(clip, geometry, GeometryCombineMode.Intersect, null);
Result:
Very important!
It looks like UIElement.Clip still render invisible parts (mayhap only with StreamGeometry) ! Never use it! Clip geometry before assigning it.
Related
I try to follow their examples on github and i just want to draw a line. But when the window opens, there only a blackscreen. I dont know what i did wrong.
Here is my source code.
Thank you for helping !
static void Main()
{
var form = new RenderForm("Test");
int width = form.ClientSize.Width;
int height = form.ClientSize.Height;
var device = new Device(new Direct3D(), 0, DeviceType.Hardware, form.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters(width, height) { PresentationInterval = PresentInterval.One });
Line line = new Line(device);
RawVector2[] vertices =
{
new RawVector2(10, 10),
new RawVector2(10, 10)
};
RenderLoop.Run(form, () =>
{
device.Clear(ClearFlags.Target, new RawColorBGRA(0, 0, 0, 1), 1.0f, 0);
device.BeginScene();
line.Width = 10;
line.GLLines = true;
line.Antialias = false;
line.Draw(vertices, new RawColorBGRA(254, 254, 254, 1));
device.EndScene();
device.Present();
});
}
This might not be your error but it won't help matters because even if there are no errors you will see very little. I have used it but only in a window context and I followed https://github.com/Dan6040/SharpDX-Rastertek-Tutorials because I was coming from C++ Unreal, Raw DX9, and C# Unity, XNA framework.
Your vertices are drawing a 1-pixel dot because you need 2 vectors at different locations to draw a line
[
new RawVector2(10, 10),
new RawVector2(10, 10)
]
That is saying draw from x:10 y:10 to x:10 y:10
try:
[
new RawVector2(10, 10),
new RawVector2(30, 30)
]
So to better explain to draw a triangle you need 3 points and 3 lines, but to draw each line you need points in your ordering
[
new RawVector2(10, 10),
new RawVector2(10, 30),
new RawVector2(30, 30),
new RawVector2(10, 10)
]
So that works by saying draw from 10,10 to 10,30 then to 30,30 and finally back to 10,10
Just to note this is why Games engines don't work through arrays of points they are arrays of pointers to points so you only have the point objects in memory once and you just using pointers to them E.G
static void Main()
{
var form = new RenderForm("Test");
int width = form.ClientSize.Width;
int height = form.ClientSize.Height;
var device = new Device(new Direct3D(), 0, DeviceType.Hardware, form.Handle, CreateFlags.HardwareVertexProcessing, new PresentParameters(width, height) { PresentationInterval = PresentInterval.One });
Line line = new Line(device);
RawVector2[] TrianglePoints=
{
new RawVector2(10, 10),
new RawVector2(10, 30),
new RawVector2(30, 30)
};
RawVector2[] vertices = {
TrianglePoints[0],
TrianglePoints[1],
TrianglePoints[2],
TrianglePoints[0]
}
RenderLoop.Run(form, () =>
{
device.Clear(ClearFlags.Target, new RawColorBGRA(0, 0, 0, 1), 1.0f, 0);
device.BeginScene();
line.Width = 10;
line.GLLines = true;
line.Antialias = false;
line.Draw(vertices, new RawColorBGRA(254, 254, 254, 1));
device.EndScene();
device.Present();
});
}
I have a project that I need to make an image follow a spline.
I build the spline using Graphics.DrawCurve through an array of Points.
I'm trying to use PointAnimationUsingPath but I can't seem to get it to work. Apparently it doesn't work in C# with Windows form.
Can someone give me a light on how to do this?
Thank you All.
-----EDIT-----
Change to a WPF UserControl as recommend in comments.
Still need some help as the shape does not move exactly following the dots, below my code:
public partial class SplineBox : UserControl
{
Point[] finalPoint;
public SplineBox()
{
InitializeComponent();
}
public void MoveShape(Point[] _path)
{
// Create a NameScope for the page so that
// we can use Storyboards.
NameScope.SetNameScope(this, new NameScope());
// Create the EllipseGeometry to animate.
EllipseGeometry animatedEllipseGeometry =
new EllipseGeometry(new Point(10, 100), 15, 15);
// Register the EllipseGeometry's name with
// the page so that it can be targeted by a
// storyboard.
this.RegisterName("AnimatedEllipseGeometry", animatedEllipseGeometry);
// Create a Path element to display the geometry.
Path ellipsePath = new Path();
ellipsePath.Data = animatedEllipseGeometry;
ellipsePath.Fill = Brushes.Blue;
ellipsePath.Margin = new Thickness(15);
SplineCanvas.Children.Add(ellipsePath);
this.Content = SplineCanvas;
// Create the animation path.
PathGeometry animationPath = new PathGeometry();
PathFigure pFigure = new PathFigure();
pFigure.StartPoint = _path[0];
PolyBezierSegment pBezierSegment = new PolyBezierSegment();
for (int p = 1; p < _path.Length; p++)
{
pBezierSegment.Points.Add(_path[p]);
}
pFigure.Segments.Add(pBezierSegment);
animationPath.Figures.Add(pFigure);
// Freeze the PathGeometry for performance benefits.
animationPath.Freeze();
// Create a PointAnimationgUsingPath to move
// the EllipseGeometry along the animation path.
PointAnimationUsingPath centerPointAnimation = new PointAnimationUsingPath();
centerPointAnimation.PathGeometry = animationPath;
centerPointAnimation.Duration = TimeSpan.FromSeconds(5);
centerPointAnimation.RepeatBehavior = RepeatBehavior.Forever;
// Set the animation to target the Center property
// of the EllipseGeometry named "AnimatedEllipseGeometry".
Storyboard.SetTargetName(centerPointAnimation, "AnimatedEllipseGeometry");
Storyboard.SetTargetProperty(centerPointAnimation,
new PropertyPath(EllipseGeometry.CenterProperty));
// Create a Storyboard to contain and apply the animation.
Storyboard pathAnimationStoryboard = new Storyboard();
pathAnimationStoryboard.RepeatBehavior = RepeatBehavior.Forever;
pathAnimationStoryboard.AutoReverse = true;
pathAnimationStoryboard.Children.Add(centerPointAnimation);
// Start the Storyboard when ellipsePath is loaded.
ellipsePath.Loaded += delegate (object sender, RoutedEventArgs e)
{
// Start the storyboard.
pathAnimationStoryboard.Begin(this);
};
}
public void Paint(ScreenObject _spline)
{
List<Point> points = new List<Point>();
if (true)
{
var spline = _spline;
foreach (System.Windows.Point point in spline.SplineAnchors)
{
Point tempP = new Point((int)point.X, (int)point.Y);
points.Add(tempP);
}
finalPoint = points.ToArray();
//Pen pen = new Pen(Color.FromArgb(255, 0, 0, 255), 1);
//e.Graphics.DrawCurve(pen, finalPoint);
foreach (Point p in finalPoint)
{
// Create a red Ellipse.
Ellipse myEllipse = new Ellipse();
// Create a SolidColorBrush with a red color to fill the
// Ellipse with.
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
// Describes the brush's color using RGB values.
// Each value has a range of 0-255.
mySolidColorBrush.Color = Color.FromArgb(255, 100, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.Black;
// Set the width and height of the Ellipse.
myEllipse.Width = 10;
myEllipse.Height = 10;
myEllipse.Margin = new Thickness(p.X - 5, p.Y - 5, 0, 0);
//e.Graphics.DrawRectangle(pen, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
//e.Graphics.FillRectangle(Brushes.Red, new Rectangle(p.X - 5, p.Y - 5, 10, 10));
SplineCanvas.Children.Add(myEllipse);
}
}
}
}
I've a class which inherits from Shape and also needs to precisely draw some multiline text within the OnRender(DrawingContect drawingContext) method.
I can fill a rectangle which exactly fills the rectangular size of the text:
And the relevant simplified code snippet:
protected override void OnRender(DrawingContext drawingContext)
{
...
var formattedText = new FormattedText(
Text,
CultureInfo.CurrentCulture,
CultureInfo.CurrentCulture.TextInfo.IsRightToLeft
? FlowDirection.RightToLeft
: FlowDirection.LeftToRight,
TypeFace,
FontSize,
TextBrush
);
formattedText.TextAlignment = TextAlignment.Left;
formattedText.Trimming = TextTrimming.CharacterEllipsis;
formattedText.SetFontWeight(FontWeight);
formattedText.MaxTextWidth = Width;
formattedText.MaxTextHeight = Height;
...
DrawShape(
drawingContext,
new List<Point>
{
new Point(0, 0),
new Point(formattedText.Width, 0),
new Point(formattedText.Width, formattedText.Height),
new Point(0, formattedText.Height)
},
brush,
pen
);
drawingContext.DrawText(formattedText, new Point(0, 0));
...
}
void DrawShape(DrawingContext dc, List<Point> points, Brush fill, Pen pen)
{
var streamGeometry = new StreamGeometry();
using (var ctx = streamGeometry.Open())
{
ctx.BeginFigure(points[0], true, true);
foreach (var point in points)
{
ctx.LineTo(point, true, true);
}
}
streamGeometry.Freeze();
dc.DrawGeometry(fill, pen, streamGeometry);
}
My problem is when I try to use the same code above but with TextAlignment.Center I'm unable to correctly position that rectangle behind the text:
How can I get the x offset to correctly draw that rectangle?
This isn't what I'm trying to achieve, but is a simplified example which highlights the issue.
There are two properties, OverhandLeading and OverhandTrailing which provide this information:
I overlooked these originally as they weren't giving me the expected behaviour, but turns out I had some incorrect logic to calculate my rotation centre point on my real shape.
So, for the example above, the points would be:
new Point(formattedText.OverhangLeading, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, 0),
new Point(formattedText.Width - formattedText.OverhangTrailing, formattedText.Height),
new Point(formattedText.OverhangLeading, formattedText.Height)
I am trying to create a 50 x 50 square that has half of the rectangle white and the other half black (the lines going vertically instead of horizontally). I have the following code, but it is not filling the rectangle as expected. How do I make it 50% white and 50% black?
System.Windows.Shapes.Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
DrawingBrush blackBrush = new DrawingBrush();
GeometryDrawing backgroundSquare = new GeometryDrawing(System.Windows.Media.Brushes.White,null,new RectangleGeometry(new Rect(25, 0, 50, 50)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(25, 0, 100, 100)));
GeometryDrawing checkers = new GeometryDrawing(new SolidColorBrush(Colors.Black), null, gGroup);
DrawingGroup checkersDrawingGroup = new DrawingGroup();
checkersDrawingGroup.Children.Add(backgroundSquare);
checkersDrawingGroup.Children.Add(checkers);
blackBrush.Drawing = checkersDrawingGroup;
blackBrush.Viewport = new Rect(0, 0, 0.25, 0.25);
blackBrush.TileMode = TileMode.Tile;
swatch.Fill = blackBrush;
sp_Thumbnails.Children.Add(swatch);
Its simple to have three sections just have one more GeometryDrawing object within your drawingGroup.
you can also configure the number of GeometryDrawing will be there within your drawingGroup as below.
Please see the generic solution to your problem that will display horizontal sections as per the groupCount value.
public void CreateRectangle(int groupCount)
{
Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
double groupsize = 100 / groupCount;
DrawingBrush blackBrush = new DrawingBrush();
DrawingGroup checkersDrawingGroup = new DrawingGroup();
//Considering 3 as groupCount
List<SolidColorBrush> brushes = new List<SolidColorBrush>() { Brushes.Black, Brushes.White,Brushes.Red };
double location = 0;
for (int i = 0; i < groupCount; i++)
{
GeometryDrawing drawing = new GeometryDrawing(brushes[i] , null,
new RectangleGeometry(new Rect(0, location,groupsize,groupsize)));
checkersDrawingGroup.Children.Add(drawing);
location += groupsize;
}
blackBrush.Drawing = checkersDrawingGroup;
swatch.Fill = blackBrush;
brdrect.Children.Add(swatch);
}
To fill your rectangle half with black and half with white. I modified your code as below. This will create a rectangle with lines separating two sections vertically.
Rectangle swatch = new System.Windows.Shapes.Rectangle();
swatch.Width = 50;
swatch.Height = 50;
DrawingBrush blackBrush = new DrawingBrush();
GeometryDrawing backgroundSquare = new GeometryDrawing(Brushes.White, null,
new RectangleGeometry(new Rect(0, 0, 25, 25)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(25, 0, 25, 25)));
GeometryDrawing checkers = new GeometryDrawing(Brushes.Black, null, gGroup);
DrawingGroup checkersDrawingGroup = new DrawingGroup();
checkersDrawingGroup.Children.Add(backgroundSquare);
checkersDrawingGroup.Children.Add(checkers);
blackBrush.Drawing = checkersDrawingGroup;
swatch.Fill = blackBrush;
brdrect.Children.Add(swatch);
If you want your sections to be spliced horizontally then you will need few changes in above code.
just modify the rectangle drawing creation section as below.
GeometryDrawing backgroundSquare = new GeometryDrawing(Brushes.White, null,
new RectangleGeometry(new Rect(0, 0, 25, 25)));
GeometryGroup gGroup = new GeometryGroup();
gGroup.Children.Add(new RectangleGeometry(new Rect(0, 25, 25, 25)));
GeometryDrawing checkers = new GeometryDrawing(Brushes.Black, null, gGroup);
I was trying to create a brush that draws a geometry. Everything worked fine until I tried to add Dashing to the shape.
I found that when I create the geometry using Geometry.Parse, the dashed line appears correctly, but when I create it directly using StreamGeometryContext, nothing gets rendered.
This is the code I'm using:
private void RenderGeometryAndSetAsBackground()
{
Point startPoint = new Point(3505961.52400725, 3281436.57325874);
Point[] points = new Point[] {
new Point(3503831.75515445,3278705.9649394),
new Point(3503905.74802898,3278449.37713916),
new Point(3507242.57331039,3276518.41148474),
new Point(3507700.6914325,3276536.23547958),
new Point(3510146.73449577,3277964.12812859),
new Point(3509498.96473447,3278678.60178448),
new Point(3507412.1889951,3277215.64022219),
new Point(3504326.22698001,3278682.85514017),
new Point(3506053.34789057,3281390.66371786)};
string geom = "M3505961.52400725,3281436.57325874L3503831.75515445,3278705.9649394 3503905.74802898,3278449.37713916 3507242.57331039,3276518.41148474 3507700.6914325,3276536.23547958 3510146.73449577,3277964.12812859 3509498.96473447,3278678.60178448 3507412.1889951,3277215.64022219 3504326.22698001,3278682.85514017 3506053.34789057,3281390.66371786";
//Geometry geometry = StreamGeometry.Parse(geom);
StreamGeometry geometry = new StreamGeometry();
using (StreamGeometryContext sgc = geometry.Open())
{
sgc.BeginFigure(startPoint, false, true);
foreach (Point p in points)
{
sgc.LineTo(p, true, true);
}
}
Pen pen = new Pen(Brushes.Yellow, 3);
pen.DashStyle = new DashStyle(new double[] { 30, 30 }, 0);
//GeometryDrawing gd = new GeometryDrawing(null, pen, path.RenderedGeometry);
GeometryDrawing gd = new GeometryDrawing(null, pen, geometry);
DrawingBrush drawingBrush = new DrawingBrush(gd);
DrawingBrush tile = drawingBrush.Clone();
tile.Viewbox = new Rect(0.5, 0, 0.25, 0.25);
RenderTargetBitmap rtb = new RenderTargetBitmap(256, 256, 96, 96, PixelFormats.Pbgra32);
var drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
context.DrawRectangle(tile, null, new Rect(0, 0, 256, 256));
}
rtb.Render(drawingVisual);
ImageBrush bgBrush = new ImageBrush(rtb);
Background = bgBrush;
}
When done that way, nothing is getting drawn. If I don't use dashing (or set the dashing to null) it works fine. It also works if I use the StreamGeometry.Parse(geom) and keeps the dashing.
Trying to call sgc.Close() didn't help. Currently my only workaround is to call:
geometry = Geometry.Parse(geometry.ToString());
which is not very nice...
What am I missing?
That's a pretty fascinating bug you got there, I can confirm it. Some ILSpy digging revealed the cause: the implicit BeginFigure call that is generated by Geometry.Parse sets the isFilled parameter to true, whereas you set it to false in your explicit StreamGeometryContext call. Change the second parameter in sgc.BeginFigure from false to true, and the dashed lines will render.
The WPF path markup syntax does not allow specifying whether any individual figure should be filled or not, so I suppose that's why the parser defaults to filling them all, just to be sure. But I can't see any good reason why dashed lines would require filled figures, that has to be a WPF bug.