I'm using DrawingContext.DrawText and DrawingContext.PushTransfrom to create rotated text on Visual Layer in WPF but as you see in the image below, the rotated text is rather blurry in some areas of the image..
Is there any option I can use to improve this? The Arial font is used for the text.
public class BeamTextDrawing : FrameworkElement
{
private readonly VisualCollection _visuals;
public BeamTextDrawing(double scale)
{
if (scale <= 0)
{
scale = 1;
}
var typeface = Settings.BeamTextTypeface;
var cultureinfo = Settings.CultureInfo;
var flowdirection = Settings.FlowDirection;
var textsize = Settings.BeamTextSize / scale;
var beamtextcolor = Settings.InPlanBeamTextColor;
_visuals = new VisualCollection(this);
foreach (var beam in Building.BeamsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = Convert.ToString(beam.Section.Id);
//text = scale.ToString();
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, textsize, beamtextcolor)
{
TextAlignment = TextAlignment.Center
};
var x1 = beam.ConnectivityLine.I.X;
var y1 = beam.ConnectivityLine.I.Y;
var x2 = beam.ConnectivityLine.J.X;
var y2 = beam.ConnectivityLine.J.Y;
var v1 = new Point(x2, y2) - new Point(x1, y1);
var v2 = new Vector(1, 0);
var hwidth = textsize;
var l = Geometrics.GetOffset(x1, y1, x2, y2, hwidth + 5/scale);
var angle = Vector.AngleBetween(v1, v2);
var x = 0.5 * (l.X1 + l.X2);
var y = 0.5 * (l.Y1 + l.Y2);
var r = new RotateTransform(angle, x, SelectableModel.FlipYAxis(y));
dc.PushTransform(r);
dc.DrawText(ft, SelectableModel.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
Update:
Here is the image after using this code:
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
I'm still getting blurry results. Look at the middle beam text at the lower part of the image.
Check out this article - Pixel Snapping in WPF Applications
Since WPF uses device independent pixels it can create irregular edge rendering due to anti-aliasing when it undergoes transformation. Pixel snapping is a means to suppress these visual artifacts by applying small offsets to the geometry of the visual to align the geometry to device pixels.
Setting the SnapsToDevicePixels Property to "True" for your UI elements should be able to fix your issue.
Related
I am trying to create a collage of images with Magick.net. I am using MagickImageCollection and .Mosaic(). I tried already a few of the functions provided by MagickImageCollection but all of them increase the brightness of the final image. The only one that worked so far was .Montage(), but with .Montage() I don't get the padding right.
How do I need to configure it, that .Mosaic() keeps the colors as they are in the single images?
using (var collection = new MagickImageCollection())
{
for (var i = 0; i < thumbnailCount; i++)
{
var image = new MagickImage(TempThumbPathFor(i));
image.Resize(256, 0);
var posX = (image.Page.Width + margin) * (i % 2);
var posY = (image.Page.Height + margin) * (i / 2);
image.Page = new MagickGeometry(posX, posY, new Percentage(100), new Percentage(100));
collection.Add(image);
}
using (var result = collection.Mosaic())
{
result.Write(newPath);
}
}
Collage of images with washed out colors:
For more information why the problem occurred in the first place have a look at this issue: GitHub
Figured out how to create a montage with padding and proper color. Couldn't get it to work with .Mosaic but with .Montage().
The important part is to add the margin to X, Y, Height and Width and call .Trim() on the final image. You will most likely have to play around a bit with the margin to get a balanced looking padding between the images, but other than that it works quite well.
const int margin = 2;
MagickGeometry geometry = null;
using (var collection = new MagickImageCollection())
{
for (var i = 0; i < thumbnailCount; i++)
{
var image = new MagickImage(TempThumbPathFor(i));
image.Resize(256, 0);
collection.Add(image);
if (i == 0)
{
geometry = image.BoundingBox;
geometry.X += margin;
geometry.Width += margin;
geometry.Y += margin;
geometry.Height += margin - 1;
}
}
using (var result = collection.Montage(new MontageSettings()
{
Geometry = geometry,
BackgroundColor = MagickColor.FromRgb(255, 255, 255)
}))
{
result.Trim();
result.Write(newPath);
}
}
I am aware of technique to draw a circle by generating points by basic math and then use the points to create a MapPolygon.
Is there another way?
For example, I see there is a class for circles:
public sealed class Geocircle : IGeocircle, IGeoshape
But I don't know how to use it, there doesn't seem to be any MapLayer for it.
Geocircle is used to create a geographic circle object for the given position and radius. It often use to make map Geofence, but not to display the cycle on the map.
There many way that draw a circle on the map
generating points by basic math
for (var i = 0; i < 360; i++)
{
//draw a cycle
BasicGeoposition point = new BasicGeoposition()
{ Latitude = centerLatitude + ri * Math.Cos(3.6 * i * 3.14 / 180), Longitude = centerLongitude + ri * Math.Sin(3.6 * i * 3.14 / 180) };
list.Add(point);
}
Add Ellipse to map
private void MyMap_Loaded(object sender, RoutedEventArgs e)
{
// Specify a known location.
BasicGeoposition snPosition = new BasicGeoposition { Latitude = 47.620, Longitude = -122.349 };
Geopoint snPoint = new Geopoint(snPosition);
// Create a XAML border.
var ellipse1 = new Ellipse();
ellipse1.Fill = new SolidColorBrush(Windows.UI.Colors.Coral);
ellipse1.Width = 200;
ellipse1.Height = 200;
// Center the map over the POI.
MyMap.Center = snPoint;
MyMap.ZoomLevel = 14;
// Add XAML to the map.
MyMap.Children.Add(ellipse1);
MapControl.SetLocation(ellipse1, snPoint);
MapControl.SetNormalizedAnchorPoint(ellipse1, new Point(0.5, 0.5));
}
You can create XAML shapes and add them as a child to the MapControl. Then you set their location using SetLocation:
var circle = new Ellipse() {
Height = 20,
Width = 20,
Fill = new SolidColorBrush(Colors.Blue)
};
map.Children.Add(circle);
var location = new Geopoint(new BasicGeoposition()
{
Latitude = 51.1789,
Longitude = -1.8261
});
map.SetLocation(circle, location);
The Geocircle class is used for geofencing, so it is not applicable here.
Background: I am currently busy with showing position of a Vehicle on a Zoomable Canvas based on the Position (X,Y) and Orientation (for Rotation). I use Rectangle for visualizing the vehicle. Everything works well but I got a bit greedy and now I want to replace the Rectangle with Top View Picture of the Vehicle, so it looks that the vehicle itself is moving instead a Rectangle.
Code Below:
private void PaintLocationVehicle(VehicleClass vc)
{
IEnumerable<Rectangle> collection = vc.ZoomableCanvas.Children.OfType<Rectangle>().Where(x => x.Name == _vehicleobjectname);
List<Rectangle> listE = collection.ToList<Rectangle>();
for (int e = 0; e < listE.Count; e++)
vc.ZoomableCanvas.Children.Remove(listE[e]);
// Assign X and Y Position from Vehicle
double drawingX = vc.gCurrentX * GlobalVar.DrawingQ;
double drawingY = vc.gCurrentY * GlobalVar.DrawingQ;
// Scale Length and Width of Vehicle
double tractorWidthScaled = vc.tractorWidth * GlobalVar.DrawingQ;
double tractorLengthScaled = vc.tractorLength * GlobalVar.DrawingQ;
// Get Drawing Location
double _locationX = drawingX - (tractorLengthScaled / 2);
double _locationY = drawingY - ((tractorWidthScaled / 2));
RotateTransform rotation = new RotateTransform();
// Angle in 10th of a Degree
rotation.Angle = vc.gCurrentTheeta/10 ;
double i = 0;
//paint the node
Rectangle _rectangle = new Rectangle();
_rectangle.Stroke = new SolidColorBrush((Color)ColorConverter.ConvertFromString(vc.VehicleColor == "" ? "Black" : vc.VehicleColor));
_rectangle.Fill = new SolidColorBrush((Color)ColorConverter.ConvertFromString(vc.VehicleColor == "" ? "Black" : vc.VehicleColor));
i += 0;
_rectangle.Width = tractorLengthScaled ;
_rectangle.Height = tractorWidthScaled;
rotation.CenterX = _rectangle.Width / 2;
rotation.CenterY = _rectangle.Height / 2;
_rectangle.RenderTransform = rotation;
Canvas.SetTop(_rectangle, _locationY + i);
Canvas.SetLeft(_rectangle, _locationX + i);
_rectangle.SetValue(ZoomableCanvas.ZIndexProperty, 2);
string _tooltipmsg = "Canvas: " + vc.ZoomableCanvas.Name;
// Assign ToolTip Values for User
_tooltipmsg += "\nX: " + vc.gCurrentX;
_tooltipmsg += "\nY: " + vc.gCurrentY;
_rectangle.ToolTip = _tooltipmsg;
_rectangle.Name = _vehicleobjectname;
//add to the canvas
vc.ZoomableCanvas.Children.Add(_rectangle);
}
Note: VehicleClass holds all the Values for a certain Vehicle. DrawingQ holds the transformation scale from Reality to Zoomable Canvas.
So the issues I forsee:
How to append the Size of a Jpeg file to get the size same as
Rectangle?
What kind of Shape object shall I use? Please
suggest.
If i undrestand you correctly. you wanted to show an image of the vechicle inside the rectangle. in order to do that you can use
ImageBrush and assign to the Rectangle Fill property
something like this
Rectangle rect = new Rectangle();
rect.Width = 100;
rect.Height = 100;
ImageBrush img = new ImageBrush();
BitmapImage bmp = new BitmapImage();
bmp.BeginInit();
bmp.UriSource = new Uri("vehicle image path");
bmp.EndInit();
img.ImageSource = bmp;
rect.Fill = img;
I hope that helps
I'm using DrawingContext.DrawText and DrawingContext.PushTransfrom to create rotated text on Visual Layer in WPF but as you see in the image below, the rotated text is rather blurry in some areas of the image..
Is there any option I can use to improve this? The Arial font is used for the text.
public class BeamTextDrawing : FrameworkElement
{
private readonly VisualCollection _visuals;
public BeamTextDrawing(double scale)
{
if (scale <= 0)
{
scale = 1;
}
var typeface = Settings.BeamTextTypeface;
var cultureinfo = Settings.CultureInfo;
var flowdirection = Settings.FlowDirection;
var textsize = Settings.BeamTextSize / scale;
var beamtextcolor = Settings.InPlanBeamTextColor;
_visuals = new VisualCollection(this);
foreach (var beam in Building.BeamsInTheElevation)
{
var drawingVisual = new DrawingVisual();
using (var dc = drawingVisual.RenderOpen())
{
var text = Convert.ToString(beam.Section.Id);
//text = scale.ToString();
var ft = new FormattedText(text, cultureinfo, flowdirection,
typeface, textsize, beamtextcolor)
{
TextAlignment = TextAlignment.Center
};
var x1 = beam.ConnectivityLine.I.X;
var y1 = beam.ConnectivityLine.I.Y;
var x2 = beam.ConnectivityLine.J.X;
var y2 = beam.ConnectivityLine.J.Y;
var v1 = new Point(x2, y2) - new Point(x1, y1);
var v2 = new Vector(1, 0);
var hwidth = textsize;
var l = Geometrics.GetOffset(x1, y1, x2, y2, hwidth + 5/scale);
var angle = Vector.AngleBetween(v1, v2);
var x = 0.5 * (l.X1 + l.X2);
var y = 0.5 * (l.Y1 + l.Y2);
var r = new RotateTransform(angle, x, SelectableModel.FlipYAxis(y));
dc.PushTransform(r);
dc.DrawText(ft, SelectableModel.FlipYAxis(x, y));
}
_visuals.Add(drawingVisual);
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
}
Update:
Here is the image after using this code:
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
I'm still getting blurry results. Look at the middle beam text at the lower part of the image.
You can change the TextFormattingMode to prevent blurry text:
public BeamTextDrawing(double scale)
{
TextOptions.SetTextFormattingMode(this, TextFormattingMode.Display);
// .. Your other code
Hi I have a path from GetFlattenedPathGeometry where i can iterate through the figures and segments to get the points to add to a PointCollection.
I then multiply each point.x/y by a scale factor to get a full scaled version of the original path data. (not using scaletransform as it doesn't suit my requirements).
If i use something like:
public static PathGeometry GetPathGeometry(PointCollection polygonCorners)
{
List<PathSegment> pathSegments = new List<PathSegment> { new PolyLineSegment(polygonCorners, true) };
PathGeometry pathGeometry = new PathGeometry();
pathGeometry.Figures.Add(new PathFigure(polygonCorners[0], pathSegments, true));
return pathGeometry;
}
It returns a new path geometry but doesn't handle ellipses with excluded path geometry in that the path is just one continuous line.
Is there a way to convert the PointCollection to Path.Data (eg: with the "M" "L" and such) for me to re-use Geometry.Parse(the new string)?
Here is the code i'm using to get the flattenedgeometry pointcollection:
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
foreach (var s in f.Segments)
{
if (s is PolyLineSegment)
{
foreach (var pt in ((PolyLineSegment) s).Points)
{
strGeom += pt.ToString();
Point ptn = new Point(pt.X * ScaleX, pt.Y * ScaleY);
pcol.Add(ptn);
}
}
}
}
< Edit Images >
Here is the original path with rectangles and ellipses subtracted from the geometry.
And here is what is looks like re-creating from the code.
If i use the original GetFlattenedPathGeometry, it looks like the original but i need to scale the points to a new resolution.
Hope this makes it clearer.
You could simply call ToString on the PathGeometry to get the whole path data string at once:
var sourceGeometry = path.Data.GetFlattenedPathGeometry();
var geometryString = sourceGeometry.ToString(CultureInfo.InvariantCulture);
var targetGeometry = Geometry.Parse(geometryString);
And why can't you just apply a ScaleTransform to the whole geometry before calling GetFlattenedPathGeometry? The following works perfectly for me (with two EllipseGeometries in an excluding CombinedGeometry):
var pathGeometry = path.Data.Clone();
pathGeometry.Transform = new ScaleTransform(0.5, 0.5);
var scaledGeometry = pathGeometry.GetFlattenedPathGeometry();
EDIT: From what you write in your question and comments, I'm guessing that all you actually want to do is to add or combine geometries with different scaling factors. If that is true, your flattened geometry approach is by far to complicated, as you could easily do that with the following two methods:
private PathGeometry AddGeometries(
Geometry geometry1, Geometry geometry2, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
var pathGeometry = PathGeometry.CreateFromGeometry(geometry1);
pathGeometry.AddGeometry(geometry2);
return pathGeometry;
}
private PathGeometry CombineGeometries(
Geometry geometry1, Geometry geometry2, GeometryCombineMode mode, double scale)
{
geometry2 = geometry2.Clone();
geometry2.Transform = new ScaleTransform(scale, scale);
return Geometry.Combine(geometry1, geometry2, mode, null);
}
Given a Path with some geometry in its Data property, you may now add (or combine) an arbitray other geometry with a scaling factor with a call like this:
Geometry newGeometry1 = ...
double scale1 = ...
path.Data = AddGeometries(path.Data, newGeometry1, scale1);
Geometry newGeometry2 = ...
double scale2 = ...
path.Data = CombineGeometries(path.Data, newGeometry2,
GeometryCombineMode.Exclude, scale2);
Found the answer by perseverance.
The code to get each point of flattenedpathgeometry and add a scale to each point and recreate the same flattenedpathgeometry with the new points. hope it helps someone. And thanks Clemens. Appreciate your efforts.
path.Data = Geometry.Parse(CurrentObject.Geometry1);
PathGeometry g = path.Data.GetFlattenedPathGeometry();
PathGeometry g = path.Data.GetFlattenedPathGeometry();
foreach (var f in g.Figures)
{
Point pt1 = f.StartPoint;
pt1.X = pt1.X * ScaleX;
pt1.Y = pt1.Y * ScaleY;
strGeom += "M" + pt1.ToString();
foreach (var s in f.Segments)
if (s is PolyLineSegment)
{
count = 0;
foreach (var pt in ((PolyLineSegment)s).Points)
{
int scount = ((PolyLineSegment)s).Points.Count;
if (count == 0)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += "L" + pts.ToString();
}
else if (count < scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString();
}
else if (count == scount)
{
Point pts = new Point(pt.X * ScaleX, pt.Y * ScaleY);
strGeom += " " + pts.ToString() + "Z";
}
count++;
}
}
}
path.Data = Geometry.Parse(strGeom);
Here's an image of the paths sent from a remote session: 1366x768 scales to 1920x1080