Create a line of 1 Pixel Images in WPF - wpf

I am creating an application in which I have to draw a Quadratic Curve. I did this using Polyline as:
BindPoints = new PointCollection();
for (int i = 0; i < 100; i++)
{
double val = i * i - 5;
var point = new Point(i, val);
Points.Add(new Point(i, i));
BindPoints.Add(point);
}
<Polyline Name="MyLine" Points="{Binding BindPoints,Mode=TwoWay} Stroke="Black" StrokeThickness="4" />
Now the issue is that I want to replace each point with an Image of 1 Pixel height. So it gives me an effect of a line but drawn with Images instead of points.
The Goal is to stack images on top of each other so they look like a line/curve.

Related

Strange drawing with DrawingContext

I'm working on a custom WPF control that should visualize thousands of graphical primitives in a scrollable area. The core part of the control's template is the following:
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:ItemVisualizer}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<local:ItemAreaElement Grid.Row="0" Grid.Column="0" x:Name="PART_ItemArea" />
<ScrollBar Grid.Row="0" Grid.Column="1" x:Name="PART_ScrollBarVert" Orientation="Vertical" Maximum="100" />
<ScrollBar Grid.Row="1" Grid.Column="0" x:Name="PART_ScrollBarHorz" Orientation="Horizontal" Maximum="100" />
<Rectangle Grid.Row="1" Grid.Column="1" x:Name="PART_SizeGrip" Focusable="False" Fill="#F0F0F0" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
For better performance, all drawing operations are performed in the OnRender method of the ItemAreaElement. To have crisp drawing, I also use the following setting in the initialization code:
this.SetValue(RenderOptions.EdgeModeProperty, EdgeMode.Aliased);
However, I have some strange issues with my drawing. To demonstrate them, I simplified the definition of my ItemAreaElement to the following:
class ItemAreaElement : FrameworkElement
{
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
const int ITEM_WIDTH = 60;
const int ITEM_HEIGHT = 20;
Pen penRed = new Pen(Brushes.Red, 1);
Pen penGreen = new Pen(Brushes.Green, 1);
int y = 0;
for (int iRow = 0; iRow < 3; iRow++)
{
int x = 0;
for (int iCol = 0; iCol < 2; iCol++)
{
Point cornerTopLeft = new Point(x, y);
dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - 1, y));
dc.DrawLine(penRed, cornerTopLeft, new Point(x, y + ITEM_HEIGHT - 1));
Point cornerBottomRight = new Point(x + ITEM_WIDTH - 1, y + ITEM_HEIGHT - 1);
dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - 1, y), cornerBottomRight);
dc.DrawLine(penGreen, new Point(x, y + ITEM_HEIGHT - 1), cornerBottomRight);
x += ITEM_WIDTH;
}
y += ITEM_HEIGHT;
}
}
}
When I launch this code on my main dev laptop equipped with an Ultra-HD screen with 282ppi (the system scale factor is 300%), I get this picture:
Or, after zooming in paint.net with gridlines:
As you see, the left, and top edges of my ItemAreaElement are partially covered by the border of the control. Must it be so? Is there a setting I can use to avoid this?
The second problem are lines that do not include the start point (see the top-left corner of my "cells"). Is this the expected behavior? IF so, how to force WPF to draw the start pixel?
And the third problem is the place or device-independent point in which the green lines should meet (the bottom-right corner of my cells). As you can see, this point is jagged. I expected to see just a green square in that place. Can I implement this with the help of the DrawingContext.DrawLine method? Or do I need to use a more complex geometry with special settings for multi-point lines, etc.?
By the way, when I launch this code on a test pc with a "classic" 96 ppi monitor and the scale factor of the OS set to 100%, the situation is a little bit better in the bottom-right corner:
But I even do not see the horizontal red lines in the top row or vertical red lines in the first column. I expected to see them there but not to be covered by the control's border.
I've managed to solve all my problems by setting the corresponding guidelines. Below you'll find the improved version of the OnRender() method presented above:
protected override void OnRender(DrawingContext dc)
{
base.OnRender(dc);
const int ITEM_WIDTH = 60;
const int ITEM_HEIGHT = 20;
const double sizeOfPen = 1;
double halfSizeOfPen = sizeOfPen / 2.0;
Pen penRed = new Pen
{
Brush = Brushes.Red,
Thickness = sizeOfPen,
StartLineCap = PenLineCap.Square,
EndLineCap = PenLineCap.Square
};
Pen penGreen = new Pen
{
Brush = Brushes.Green,
Thickness = sizeOfPen,
StartLineCap = PenLineCap.Square,
EndLineCap = PenLineCap.Square
};
int y = 0;
for (int iRow = 0; iRow < 3; iRow++)
{
int x = 0;
for (int iCol = 0; iCol < 2; iCol++)
{
GuidelineSet guidelines = new GuidelineSet();
guidelines.GuidelinesX.Add(x);
guidelines.GuidelinesX.Add(x + ITEM_WIDTH);
guidelines.GuidelinesY.Add(y);
guidelines.GuidelinesY.Add(y + ITEM_HEIGHT);
dc.PushGuidelineSet(guidelines);
Point cornerTopLeft = new Point(x + halfSizeOfPen, y + halfSizeOfPen);
dc.DrawLine(penRed, cornerTopLeft, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen));
dc.DrawLine(penRed, cornerTopLeft, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen));
Point cornerBottomRight = new Point(x + ITEM_WIDTH - halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen);
dc.DrawLine(penGreen, new Point(x + ITEM_WIDTH - halfSizeOfPen, y + halfSizeOfPen), cornerBottomRight);
dc.DrawLine(penGreen, new Point(x + halfSizeOfPen, y + ITEM_HEIGHT - halfSizeOfPen), cornerBottomRight);
dc.Pop();
x += ITEM_WIDTH;
}
y += ITEM_HEIGHT;
}
}

How to draw a zigzag line?

I am creating a document based application and i want to draw a horizontal line underlying the text. But, line should not be straight. i want to draw a line like this.
Currently i am using System.Graphics object to draw any object.
private void DrawLine(Graphics g, Point Location, int iWidth)
{
iWidth = Convert.ToInt16(iWidth / 2);
iWidth = iWidth * 2;
Point[] pArray = new Point[Convert.ToInt16(iWidth / 2)];
int iNag = 2;
for (int i = 0; i < iWidth; i+=2)
{
pArray[(i / 2)] = new Point(Location.X + i , Location.Y + iNag);
if (iNag == 0)
iNag = 2;
else
iNag = 0;
}
g.DrawLines(Pens.Black, pArray);
}
UPDATED:
Above code is working fine and line draws perfectly but, this code effects on application performance. Is there another way to do this thing.
If you want fast drawing just make a png image of the line you want, with width larger than you need and then draw the image:
private void DrawLine(Graphics g, Point Location, int iWidth)
{
Rectangle srcRect = new Rectangle(0, 0, iWidth, zigzagLine.Height);
Rectangle dstRect = new Rectangle(Location.X, Location.Y, iWidth, zigzagLine.Height);
g.DrawImage(zigzagLine, dstRect, srcRect, GraphicsUnit.Pixel);
}
zigzagLine is the bitmap.
valter

Avoiding seam in 3D cylinder model

I have this texture representing internal area of a pipe:
I have created a simple cylinder model (GeometryModel3D) by dissecting the above texture to a grid of points and than wrapping this to form a cylinder, then mapping the above texture to it (each square on a grid having two triangles):
The left and right edges of the texture meet at the above black seam line, which is unwanted.
I could not use single vertex point for smooth continuation there, because that point corresponds to both 0 and 360 degrees which is single angle, but two distinct points on edges of the 2D texture.
So I have used pairs of vertices on the same location but each corresponding to different point in the source texture.
Is there anything I can do to hide the seam line?
Model creation code:
private void SceneViewerWindowOnLoaded(object sender, RoutedEventArgs e)
{
var imageSource = new BitmapImage(new Uri("pack://application:,,,/Resources/out1.jpg"));
var meshGeometry3D = new MeshGeometry3D();
double circumference = imageSource.PixelWidth;
double radius = (circumference / (2.0 * Math.PI));
double angleSegment = (2.0 * Math.PI / SegmentCountTrans);
double sizeSegmentLong = ((double)imageSource.PixelHeight / SegmentCountLong);
double sizeSegmentTrans = ((double)imageSource.PixelWidth / SegmentCountTrans);
for (int indexSegmentLong = 0; indexSegmentLong < SegmentCountLong; indexSegmentLong++)
{
double depth = (indexSegmentLong * sizeSegmentLong);
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
meshGeometry3D.Positions.Add(GetVertexPosition(indexSegmentTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(indexSegmentTrans * sizeSegmentTrans, depth));
}
// add one extra vertex representing 360 degrees for complete wrap-around
meshGeometry3D.Positions.Add(GetVertexPosition(SegmentCountTrans, depth, radius, angleSegment));
meshGeometry3D.TextureCoordinates.Add(new Point(SegmentCountTrans * sizeSegmentTrans, depth));
}
meshGeometry3D.TriangleIndices.Clear();
for (int indexSegmentLong = 0; indexSegmentLong < (SegmentCountLong - 1); indexSegmentLong++)
{
for (int indexSegmentTrans = 0; indexSegmentTrans < SegmentCountTrans; indexSegmentTrans++)
{
int indexCurrent = (indexSegmentLong * (SegmentCountTrans + 1) + indexSegmentTrans);
meshGeometry3D.TriangleIndices.Add(indexCurrent);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + 1);
meshGeometry3D.TriangleIndices.Add(indexCurrent + SegmentCountTrans + 2);
}
}
var geometryModel3D = new GeometryModel3D
{
Geometry = meshGeometry3D,
Material = new DiffuseMaterial
{
Brush = new ImageBrush(imageSource)
}
};
this.SceneViewer.Model = geometryModel3D;
}
private static Point3D GetVertexPosition(int indexSegmentAngle, double depth, double radius, double angleSegment)
{
double angle = (StartAngle + indexSegmentAngle * angleSegment);
return new Point3D(
radius * Math.Cos(angle),
radius * Math.Sin(angle),
-depth);
}
XAML code for the Viewport3D element containing the model:
<Viewport3D x:Name="Viewport3D">
<Viewport3D.Camera>
<PerspectiveCamera x:Name="Camera" Position="0.0,0.0,0.0" LookDirection="0.0,0.0,-1.0" UpDirection="0.0,1.0,0.0" FieldOfView="90"/>
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<AmbientLight Color="White"/>
</ModelVisual3D.Content>
</ModelVisual3D>
<ModelVisual3D x:Name="ModelVisualModel"/>
</Viewport3D>
When you create your image brush try setting the TileMode to TileMode.Tile and ViewportUnits to BrushMappingMode.Absolute. If that still doesn't work then post a follow-up comment here and I'll try to reproduce it on my end.
I used DrawingBrush instead of Imagebrush. This fixed the seams and I also get an fps boost when setting the cachinghint.
Brush brush = new DrawingBrush(new ImageDrawing(_texture, new Rect(0,0,1,1)));
RenderOptions.SetCachingHint(brush , CachingHint.Cache);
RenderOptions.SetCacheInvalidationThresholdMinimum(brush , 0.5);
RenderOptions.SetCacheInvalidationThresholdMaximum(brush , 2.0);

How to create a circle in 3D with known center point, radius and it is on a plane which perpendicular to a line in WPF?

How to create a circle in 3D with known center point, radius and it is on a plane which perpendicular to a line (vector) in WPF?
Following is an example based on the comment I posted earlier.
First we define a function to generate the model of the circle:
/// <summary>
/// Generates a model of a Circle given specified parameters
/// </summary>
/// <param name="radius">Radius of circle</param>
/// <param name="normal">Vector normal to circle's plane</param>
/// <param name="center">Center position of the circle</param>
/// <param name="resolution">Number of slices to iterate the circumference of the circle</param>
/// <returns>A GeometryModel3D representation of the circle</returns>
private GeometryModel3D GetCircleModel(double radius, Vector3D normal, Point3D center, int resolution)
{
var mod = new GeometryModel3D();
var geo = new MeshGeometry3D();
// Generate the circle in the XZ-plane
// Add the center first
geo.Positions.Add(new Point3D(0, 0, 0));
// Iterate from angle 0 to 2*PI
double t = 2 * Math.PI / resolution;
for (int i = 0; i < resolution; i++)
{
geo.Positions.Add(new Point3D(radius * Math.Cos(t * i), 0, -radius * Math.Sin(t * i)));
}
// Add points to MeshGeoemtry3D
for (int i = 0; i < resolution; i++)
{
var a = 0;
var b = i + 1;
var c = (i < (resolution - 1)) ? i + 2 : 1;
geo.TriangleIndices.Add(a);
geo.TriangleIndices.Add(b);
geo.TriangleIndices.Add(c);
}
mod.Geometry = geo;
// Create transforms
var trn = new Transform3DGroup();
// Up Vector (normal for XZ-plane)
var up = new Vector3D(0, 1, 0);
// Set normal length to 1
normal.Normalize();
var axis = Vector3D.CrossProduct(up, normal); // Cross product is rotation axis
var angle = Vector3D.AngleBetween(up, normal); // Angle to rotate
trn.Children.Add(new RotateTransform3D(new AxisAngleRotation3D(axis, angle)));
trn.Children.Add(new TranslateTransform3D(new Vector3D(center.X, center.Y, center.Z)));
mod.Transform = trn;
return mod;
}
Setting up our ViewPort3D:
<Grid Background="Black">
<Viewport3D Name="mainViewPort">
<Viewport3D.Camera>
<PerspectiveCamera NearPlaneDistance="0.1" FarPlaneDistance="100" UpDirection="0,1,0" Position="0,0,10"
LookDirection="0,0,-1" FieldOfView="60" />
</Viewport3D.Camera>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup>
<AmbientLight Color="#40FFFFFF" />
<DirectionalLight Color="#FFFFFF" Direction="1,-1,-1" />
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D>
</Grid>
Then to test:
private void AddCircleModel()
{
var mod = GetCircleModel(1.5, new Vector3D(0, 1, 1), new Point3D(0, -1, 0), 20);
mod.Material = new DiffuseMaterial(Brushes.Silver);
var vis = new ModelVisual3D() { Content = mod };
mainViewPort.Children.Add(vis);
}
Load up your window, call AddCircleModel(); and enjoy. Adjust view/parameters to your heart's content.
old thread but still, if anyone comes accross.
The "newb" Answer would work but is quitte lazy math and time consuming wize, here's why :
there are 2 for loops where really 1 is needed.
using transform after building a circle on (x,z) plane centered on (0,0,0) is overkill since you know the maths and vectors constraints.
I would suggest you implement it that way (i made it so it works for ellipses too, just set uSize = vSize for circles, u and v really just need to no be parallel for it to work but for mesh quality purposes i force them to be perpendicular):
public static Mesh CreateEllipse3D(Vector3 center, Vector3 normal, Vector3 u,
Vector3 v, float uSize, float vSize, int
subdiv = 30)
{
if (Math.Abs(Vector3.Dot(u, v)) != 0)
{
Debug.LogError("Vectors u and v must be orthogonals");
return new Mesh();
}
var mesh = new Mesh();
var vertices = new List<Vector3>();
var normals = new List<Vector3>();
var triangles = new List<int>();
vertices.Add(center);
float t = 2 * Mathf.PI / subdiv;
int a = 0, b, c;
for (int i = 0; i < subdiv; i++)
{
vertices.Add(center + u.normalized * uSize * Mathf.Cos(t * i) -
v.normalized * vSize * Mathf.Sin(t * i));
b = i + 1;
c = (i < (subdiv - 1)) ? i + 2 : 1;
triangles.Add(a);
triangles.Add(b);
triangles.Add(c);
normals.Add(normal);
normals.Add(normal);
normals.Add(normal);
}
mesh.vertices = vertices.ToArray();
mesh.triangles = triangles.ToArray();
mesh.normals = normals.ToArray();
return mesh;
}

How to draw a rectangle in WPF at a specific x,y screen location?

In C#, WPF I've created a rectangle:
Rectangle myRgbRectangle = new Rectangle();
myRgbRectangle.Width = 1;
myRgbRectangle.Height = 1;
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
Yes, I really just want it to be 1 pixel by 1 pixel. And I want to change the color based on the variable height like so:
mySolidColorBrush.Color = Color.FromArgb(255, 0, 0, (byte)height);
myRgbRectangle.Fill = mySolidColorBrush;
Now, how do I draw at a specific x,y location on the screen? I do have a grid (myGrid) on my MainWindow.xaml.
Thanks!
Here's the pertinent code:
myRgbRectangle.Width = 1;
myRgbRectangle.Height = 1;
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
int height;
for (int i = 0; i < ElevationManager.Instance.heightData.GetLength(0); i++)
for (int j = 0; j < ElevationManager.Instance.heightData.GetLength(1); j++)
{
height = ElevationManager.Instance.heightData[i, j] / 100;
// Describes the brush's color using RGB values.
// Each value has a range of 0-255.
mySolidColorBrush.Color = Color.FromArgb(255, 0, 0, (byte)height);
myRgbRectangle.Fill = mySolidColorBrush;
myCanvas.Children.Add(myRgbRectangle);
Canvas.SetTop(myRgbRectangle, j);
Canvas.SetLeft(myRgbRectangle, i);
And it's throwing this error: Specified Visual is already a child of another Visual or the root of a CompositionTarget.
You need to use a Canvas istead of a Grid. You use coordinates to position elements in a Canvas versus Column and Row in a Grid.
Definition of a Canvas:
Defines an area within which you can explicitly position child elements by using coordinates that are relative to the Canvas area.
You would then use Canvas.SetTop and Canvas.SetLeft Properties like this (assuming that your canvas is named myCanvas):
myCanvas.Children.Add(myRgbRectangle);
Canvas.SetTop(myRgbRectangle, 50);
Canvas.SetLeft(myRgbRectangle, 50);
Edit
Based on your edit, it is like I said you are adding the same rectangle more than once. You need to be creating it in your For Loop each time you add it. Something like this.
for (int i = 0; i < ElevationManager.Instance.heightData.GetLength(0); i++)
for (int j = 0; j < ElevationManager.Instance.heightData.GetLength(1); j++)
{
Rectangle rect = new Rectangle();
rect.Width = 1;
rect.Height = 1;
height = ElevationManager.Instance.heightData[i, j] / 100;
// Describes the brush's color using RGB values.
// Each value has a range of 0-255.
mySolidColorBrush.Color = Color.FromArgb(255, 0, 0, (byte)height);
rect.Fill = mySolidColorBrush;
myCanvas.Children.Add(rect);
Canvas.SetTop(rect, j);
Canvas.SetLeft(rect, i);
}

Resources