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.
Related
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 have a bug in my image editor with the blur tool.
When I select the rectangle to set the blur effect, and when I apply, result is a bit different see:
To create the "Before" I do:
var blurredImage = ExtractImageToBlur(); // extract the selected area from image
BlurredImageRectangle.Fill = new ImageBrush(blurredImage);
var effect = new BlurEffect();
effect.KernelType = KernelType.Gaussian;
effect.RenderingBias = RenderingBias.Quality;
effect.Radius = m_blurValue;
BlurredImageRectangle.Effect = effect;
To create the "After", I do:
var blurredImage = ExtractImageToBlur(); // extract the selected area from image
Rectangle rectangleToRender = new Rectangle();
rectangleToRender.Fill = new ImageBrush(blurredImage);
var effect = new BlurEffect();
effect.KernelType = KernelType.Gaussian;
effect.RenderingBias = RenderingBias.Quality;
effect.Radius = m_blurValue;
rectangleToRender.Effect = effect;
Size size = new Size(croppedImg.PixelWidth, croppedImg.PixelHeight);
rectangleToRender.Measure(size);
rectangleToRender.Arrange(new Rect(size));
var render = new RenderTargetBitmap(croppedImg.PixelWidth, croppedImg.PixelHeight, 96, 96, PixelFormats.Pbgra32);
render.Render(rectangleToRender);
// Merge the source with the blurred section
DrawingVisual drawingVisual = new DrawingVisual();
using (DrawingContext context = drawingVisual.RenderOpen())
{
int left = (int)(Canvas.GetLeft(BlurredImageRectangle) * WidthRatio);
int top = (int)(Canvas.GetTop(BlurredImageRectangle) * HeightRatio);
context.DrawImage(Source, new Rect(0, 0, Source.PixelWidth, Source.PixelHeight));
context.DrawImage(render, new Rect(left, top, croppedImg.PixelWidth, croppedImg.PixelHeight));
}
var bitmap = new RenderTargetBitmap(Source.PixelWidth, Source.PixelHeight, 96, 96, PixelFormats.Pbgra32);
bitmap.Render(drawingVisual);
And when I play with the blur radius, its sometimes a lot more different from both images.
Why its not the same?
Found the problem.
When I was drawing the rectangle on the screen, I applied the blur effect on the pixels on the screen.
When I hit save, the blur effect is applied on the pixel on the image on disk.
Huge difference.
I want to do a perfectly simple thing. I want to draw two quadrilateral polygons, which have the top and bottom lines as parallel diagonals. I also want to draw these polygons stacked on top of each other.
However, after I draw the two polygons, there is a tiny but visible gap between them. It looks like this:
Here's the code that I use (I'm with Silverlight, which means WriteableBitmap):
var bmp = new System.Windows.Media.Imaging.WriteableBitmap((int)this.displayImage.Width, (int)this.displayImage.Height);
var polygon = new System.Windows.Shapes.Polygon()
{
StrokeThickness = 0,
Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black),
Points = new System.Windows.Media.PointCollection()
{
new System.Windows.Point(10, 10),
new System.Windows.Point(100, 40),
new System.Windows.Point(100, 130),
new System.Windows.Point(10, 100)
}
};
var polygon2 = new System.Windows.Shapes.Polygon()
{
StrokeThickness = 0,
Fill = new System.Windows.Media.SolidColorBrush(System.Windows.Media.Colors.Black),
Points = new System.Windows.Media.PointCollection()
{
new System.Windows.Point(10, 100),
new System.Windows.Point(100, 130),
new System.Windows.Point(100, 220),
new System.Windows.Point(10, 190)
}
};
bmp.Render(polygon, null);
bmp.Invalidate();
bmp.Render(polygon2, null);
bmp.Invalidate();
displayImage.Source = bmp;
Here displayImage is an Image control on the main grid.
What do I do to get rid of this gap?
P.S. If possible, I'd like solutions that do not use the WriteableBitmapEx library, as I don't want to drag an entire library into my project for such a small task. Also, if possible, I'd like to leave the polygons' size and coordinates the same.
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.
I am attempting to print a JPEG file that I reference using a Uri object and am having some difficulties. I found that while the image was printing, it was cropped slightly and was flipped and mirrored. I'm guessing that the crop was caused by a size not being set properly but have no idea why it's being flipped and rotated. Assuming that this was a natural oddity, I attempted to resolve the issue by applying a transform to the drawingContext object but this results a blank page being printed. Here is my code:
public void Print(List<Uri> ListToBePrinted)
{
XpsDocumentWriter writer =
PrintQueue.CreateXpsDocumentWriter(this.SelectedPrinter.PrintQueue);
PrintCapabilities printerCapabilities =
this.SelectedPrinter.PrintQueue.GetPrintCapabilities();
Size PageSize =
new Size(printerCapabilities.PageImageableArea.ExtentWidth,
printerCapabilities.PageImageableArea.ExtentHeight);
foreach (Uri aUri in ListToBePrinted)
{
BitmapImage anImage = new BitmapImage(aUri);
//create new visual which would be initialized by image
DrawingVisual drawingVisual = new DrawingVisual();
//create a drawing context so that image can be rendered to print
DrawingContext drawingContext = drawingVisual.RenderOpen();
// Flips along X and Y axis (flips and mirrors)
drawingContext.PushTransform(new ScaleTransform(-1, -1));
drawingContext.DrawImage(anImage, new Rect(PageSize));
drawingContext.Close();
writer.Write(drawingVisual);
}
}
Any help would be greatly appreciated - thank you!
Here's what I ended up with:
public void Print(List<Uri> ListToBePrinted)
{
XpsDocumentWriter writer =
PrintQueue.CreateXpsDocumentWriter(this.SelectedPrinter.PrintQueue);
PrintCapabilities printerCapabilities =
this.SelectedPrinter.PrintQueue.GetPrintCapabilities();
Size PrintableImageSize =
new Size(printerCapabilities.PageImageableArea.ExtentWidth,
printerCapabilities.PageImageableArea.ExtentHeight);
foreach (Uri aUri in ListToBePrinted)
{
DrawingVisual drawVisual = new DrawingVisual();
ImageBrush imageBrush = new ImageBrush();
imageBrush.ImageSource = new BitmapImage(aUri);
imageBrush.Stretch = Stretch.Fill;
imageBrush.TileMode = TileMode.None;
imageBrush.AlignmentX = AlignmentX.Center;
imageBrush.AlignmentY = AlignmentY.Center;
using (DrawingContext drawingContext = drawVisual.RenderOpen())
{
// Flips along X and Y axis (flips and mirrors)
drawingContext.PushTransform(new ScaleTransform(-1, 1, PrintableImageSize.Width / 2, PrintableImageSize.Height / 2));
drawingContext.PushTransform(new RotateTransform(180, PrintableImageSize.Width / 2, PrintableImageSize.Height / 2)); // Rotates 180 degree
drawingContext.DrawRectangle(imageBrush, null, new Rect(25, -25, PrintableImageSize.Width, PrintableImageSize.Height));
}
writer.Write(drawVisual);
}
}
The image is a little fuzzy but is certainly acceptable. I'm still not sure why my image needed to be flipped or mirrored.
Could you do something like:
BitmapImage anImage = new BitmapImage(aUri);
Image image = new Image();
image.BeginInit();
image.Source = anImage;
image.EndInit();
image.Measure(PageSize);
image.InvalidateVisual();
Then just print the Image object since it derives from Visual...
You need to call InvalidateVisual so that OnRender will be called, if you didn't it would result in a blank image...