Is there an automatic way to get all the points of an ellipse stroke, without the filling points?
In WPF there are no actual "Points" in a geometry - it is infinitely smooth. This can be seen by zooming in on an ellipse. You can go to 1,000,000x zoom and you can still see curvature and no points.
Since WPF shapes aren't composed of points, your question can be interepted in several ways. You may be looking for any of these:
A list of points that approximates the boundary of the ellipse (polyline approximation)
A set of pixels covered by the ellipse including the fill
A set of pixels covered by the edge of the ellipse
Here are the solutions in each case:
If you're looking for an approximation of the ellipse as discrete points (ie. a dotted-line version that looks like an ellipse), use this code:
PolyLineSegment segment =
ellipse.DefiningGeometry
.GetFlattenedPathGeometry(1.0, ToleranceType.Absolute)
.Figures[0].Segments[0] as PolyLineSegment;
foreach(Point p in segment.Points)
...
If you're looking for the pixels affected, you'll need to RenderTargetBitmap:
RenderTargetBitmap rtb =
new RenderTargetBitmap(width, height, 96, 96, PixelFormat.Gray8);
rtb.Render(ellipse);
byte[] pixels = new byte[width*height];
rtb.CopyPixels(pixels, width, 0);
Any nonzero value in pixels[] is partially covered by the ellipse. This will include points interior to the ellipse if the ellipse has a fill.
If you need to get only the pixels along the edge but your ellipse is filled, or vice versa, you can create a new Shape to pass to RenderTargetBitmap:
var newEllipse = new Path
{
Data = ellipse.DefiningGeometry,
Stroke = Brushes.Black,
};
RenderTargetBitmap rtb = ...
[same as before]
By using Reflector I found out that there is a GetPointList() method in the EllipseGeometry class unfortunately it's private. Maybe you can invoke it through reflection but that's sounds like a very bad hack... I'll see if I find another way...
Related
Many graphics packages allow the user to select where they would like to draw the border of a region around a shape; either along the inside, outside or centre of the shape. For example, this shows the same square with the border drawn along the centre, inside and outside respectively:
I could scale the path up/down based on the stroke's width, but I wanted to check if there was built-in support for this first.
I'm using Ruby, but if there's a C method for this, it's likely available in the Ruby bindings as well.
Is there a method to draw a stroke around the outside or inside of a path, rather than along the centre, in Cairo?
No, there is no such method built-in.
One could likely approximate this with a temporary surface that is later used as a mask. For example, to do "outside", you first fill a temporary surface with "transparent", then stroke with twice your desired line width some "opaque", and finally fill the shape with "transparent" to get rid of the inner part of the line width. The resulting surface can then be used as a mask.
"Inside" would be similar, but with an extra trick: Again, transparent surface and stroke with twice the line width. Now the outside part of this stroke needs to be removed. For this, one needs a path with a winding rule of even-odd. Add a surface-sized rectangle to this path inverts the path, thus allowing to remove everything outside via a fill.
For a non-zero winding rule... I do not have any immediate ideas (well, another temporary surface that is then inverted via a full-surface-paint with operator SUBTRACT?).
Sample code for drawing outside of the path (see comments):
static void draw_outside_of_path(cairo_t *cr) {
double line_width = cairo_get_line_width(cr);
cairo_pattern_t *mask;
cairo_push_group_with_content(cr, CAIRO_CONTENT_ALPHA);
cairo_set_line_width(cr, 2 * line_width);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_stroke_preserve(cr);
cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
cairo_fill_preserve(cr);
mask = cairo_pop_group(cr);
cairo_mask(cr, mask);
cairo_pattern_destroy(mask);
}
For stroking inside a path, set the path as a clipping region, then stroke the path; any part of stroke that lies outside the clipping region will be unseen...
In WPF ,we can use VisualBrush do some thing like ppt's left side.
But I see the VisualBrush may lost the line in Rectangle when I zoom the VisualBrush to a small size.Like the image:
You can see VisualBrush lost the Bottom line.
But what I want is like the below image:
When I try use the BitmapImage that use RenderTargetBitmap to get a image and use linear interpolation algorithm to zoom will get a clearness image.
Can I change VisualBrush's algorithm I think it may use neighborhood-pixels algorithm.
Are there any printscreen algorithm that have a good performance like VisualBrush.
When I change my search key to ViewBox ,I can find the same question as this one :how to avoid a single pixel line disappear in wpf?
There is a class named TransformedBitmap which can scale your RenderTargetBitmap with default scaling algorithm.
Use the code below:
public static BitmapSource ToBitmapSource(this Visual visual, Size size)
{
var bounds = VisualTreeHelper.GetDescendantBounds(visual);
var width = (int) Math.Round(bounds.Width);
var height = (int) Math.Round(bounds.Height);
var bitmap = new RenderTargetBitmap(width, height, 96.0, 96.0, PixelFormats.Pbgra32);
bitmap.Render(visual);
return new TransformedBitmap(bitmap, new ScaleTransform(size.Width / width, size.Height / height));
}
I've tried this method in my demo and got the result below. You may noticed that the small rectangle in the left-top corner lost nothing.
I'm creating graphs using JFreeChart:
The problem should be fairly clear. My circles which I'm drawing on the graph are showing up as ovals, since my graph is being scaled down to fit within my dimensions.
Here's how I'm drawing the circle annotations:
chart.getXYPlot().addAnnotation(
new XYShapeAnnotation(
new Ellipse2D.Float(pointX - 15, pointY - 15, 30, 30),
new BasicStroke(0.5), Color.BLACK, Color.GREEN
)
);
How can I draw an annotation without it being scaled down? Is there a way to draw on top of the graph, translating global/real X/Y points into local/scaled X/Y points?
As an alternative, try one of the scale-invariant annotations such as XYImageAnnotation or XYPointerAnnotation. For example,
chart.getXYPlot().addAnnotation(
new XYPointerAnnotation("Bam!", pointX, pointY, 0));
I suggest using a second series with only one single value in it. For this second series you need to enable the drawing of shapes using the setSeriesShapesVisible() method of the plot renderer of the chart. All you need to do is adding one value to this second series in the point you want the shape to appear.
You can use squares, circles, rounded rectangle and some more. In fact any java.awt.Shape object is valid.
I derive from shape, this is what is in the DefiningGeometry
protected override Geometry DefiningGeometry
{
get
{
topLeft.X = Math.Min(Start.X, End.X);
topLeft.Y = Math.Min(Start.Y, End.Y);
width.X = Math.Abs(Start.X - End.X);
width.Y = Math.Abs(Start.Y - End.Y);
rectBounds.X = topLeft.X;
rectBounds.Y = topLeft.Y;
rectBounds.Width = width.X;
rectBounds.Height = width.Y;
rectGeo.Rect = rectBounds;
return rectGeo;
}
}
I see the fill, but not the stroke, since the sroke is additional to the width and height I tried to make some room for it by setting:
Width = width.X + StrokeThickness;
//same for height.
But then nothing gets drawn, does anyone know what I am doing wrong? By the way the background and the stroke brush are different color.
Stroke is always on top of Fill. Thicknesses below 1.0 are no problem at all, although very thin strokes naturally tend to become invisible.
Fill exactly fills the Shape's geometry. Stroke renders the geometry's outline, half of the stroke lying inside, half outside the shape.
Never add StrokeThickness to your Shape's width (which would only work as you expect on rectangles anyway). See the MSDN for how the Shape's properties behave.
Do not derive from Shape to create simple geometric objects. Use the predefined Rectangle, Ellipse, Line etc. Use Path for more complex geometries and set Path.Data.
Also consult the Shapes and Basic Drawing in WPF Overview and maybe the Geometry Overview in the MSDN.
StrokeThickness for some reason should be larger than 1 (I am guessing the Fill brush is covering it), or don't set the Fill property, and StrokeThickness 1 works.
I'm trying to do simple drawing in a subclass of a decorator, similar to what they're doing here...
How can I draw a border with squared corners in wpf?
...except with a single-pixel border thickness instead of the two they're using there. However, no matter what I do, WPF decides it needs to do its 'smoothing' (e.g. instead of rendering a single-pixel line, it renders a two-pixel line with each 'half' about 50% of the opacity.) In other words, it's trying to anti-alias the drawing. I do not want anti-aliased drawing. I want to say if I draw a line from 0,0 to 10,0 that I get a single-pixel-wide line that's exactly 10 pixels long without smoothing.
Now I know WPF does that, but I thought that's specifically why they introduced SnapsToDevicePixels and UseLayoutRounding, both of which I've set to 'True' in the XAML. I'm also making sure that the numbers I'm using are actual integers and not fractional numbers, but still I'm not getting the nice, crisp, one-pixel-wide lines I'm hoping for.
Help!!!
Mark
Aaaaah.... got it! WPF considers a line from 0,0 to 10,0 to literally be on that logical line, not the row of pixels as it is in GDI. To better explain, think of the coordinates in WPF being representative of the lines drawn on a piece of graph paper whereas the pixels are the squares those lines make up (assuming 96 DPI that is. You'd need to adjust accordingly if they are different.)
So... to get the drawing to refer to the pixel locations, we need to shift the drawing from the lines themselves to be the center of the pixels (squares on graph paper) so we shift all drawing by 0.5, 0.5 (again, assuming a DPI of 96)
So if it is a 96 DPI setting, simply adding this in the OnRender method worked like a charm...
drawingContext.PushTransform(new TranslateTransform(.5, .5));
Hope this helps others!
M
Have a look at this article: Draw lines exactly on physical device pixels
UPD
Some valuable quotes from the link:
The reason why the lines appear blurry, is that our points are center
points of the lines not edges. With a pen width of 1 the edges are
drawn excactly between two pixels.
A first approach is to round each point to an integer value (snap to a
logical pixel) an give it an offset of half the pen width. This
ensures, that the edges of the line align with logical pixels.
Fortunately the developers of the milcore (MIL stands for media
integration layer, that's WPFs rendering engine) give us a way to
guide the rendering engine to align a logical coordinate excatly on a
physical device pixels. To achieve this, we need to create a
GuidelineSet
protected override void OnRender(DrawingContext drawingContext)
{
Pen pen = new Pen(Brushes.Black, 1);
Rect rect = new Rect(20,20, 50, 60);
double halfPenWidth = pen.Thickness / 2;
// Create a guidelines set
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(rect.Left + halfPenWidth);
guidelines.GuidelinesX.Add(rect.Right + halfPenWidth);
guidelines.GuidelinesY.Add(rect.Top + halfPenWidth);
guidelines.GuidelinesY.Add(rect.Bottom + halfPenWidth);
drawingContext.PushGuidelineSet(guidelines);
drawingContext.DrawRectangle(null, pen, rect);
drawingContext.Pop();
}