Extra lines appear when EdgeMode.Aliased option is used in WPF - c#

I have encountered a very odd problem today using WPF. Here is the code I have used to draw 10000 LineGeometry objects.
// Draw 10000 lines
var g = new GeometryGroup();
var x = 0;
var y = 0;
var n = 1;
while (n < 10000)
{
x = x + 20;
if (x > 600)
{
x = 0;
y = y + 20;
}
var l = new LineGeometry
{
StartPoint = new Point(x, y),
EndPoint = new Point(x, y + 15)
};
g.Children.Add(l);
n++;
}
var drawing = new GeometryDrawing {Geometry = g};
var drawingGroup = new DrawingGroup();
drawingGroup.Children.Add(drawing);
var myPen = new Pen {Thickness = 1, Brush = Brushes.Yellow};
drawing.Pen = myPen;
var myImage = new Image {Stretch = Stretch.None, Margin = new Thickness(10)};
var myDrawingImage = new DrawingImage {Drawing = drawingGroup};
myImage.Source = myDrawingImage;
canvas.Children.Add(myImage);
Now as you can see the result is not crisp and I used the code below to achieve better results.
RenderOptions.SetEdgeMode(myImage, EdgeMode.Aliased);
The image gets crisp but has another side effects as you can see in the image below.
Some lines are now shown.
Some extra odd lines are shown.
I have included the full code so you can experiment this for yourself.
Note:
I'm using ZoomBorder class around the Canvas canvas.
Pan & Zoom Image

Have you tried using
RenderOptions.SetBitmapScalingMode(myImage, BitmapScalingMode.HighQuality);
instead?
You can also set it on your Canvas control as well..
<Canvas RenderOptions.BitmapScalingMode="HighQuality"> ...

This is not a real answer but I want add something to that odd behavior of WPF in hope somebody can come up with a real explanation.
I added a single line from (0,0)-(20000,20000). Creating 8000 line objects (n < 8000) produces the expected result like this:
Now, creating 9000 line objects will totally mess it up, but see for yourself:
Without setting EdgeMode.Aliased it looks fine, even with high counts of objects.

Related

Creating a Mosaic out of multiple images increases the luminance of the final image

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);
}
}

Dynamically creating charts

I am trying to dynamically create a chart for each drive in the computer, inside a form.
Each chart should be a pie chart that contains the amount of free space (colored green) and used space(colored red) in GBs.
But when I run the following code the only thing I see is blank rectangles with the titles of "C:\", "D:\" and so on.
Here is the code :
public static void DrawCharts()
{
Chart[] charts = new Chart[DriveInfo.GetDrives().Length];
DriveInfo[] drives = DriveInfo.GetDrives();
for (int i = 0; i < drives.Length; i++)
{
charts[i] = new Chart();
charts[i].Palette = ChartColorPalette.BrightPastel;
charts[i].Titles.Add(drives[i].Name);
charts[i].Series.Add("Storage");
charts[i].Series[0].ChartType = SeriesChartType.Pie;
charts[i].Location = new System.Drawing.Point(20 + i * 231, 30);
charts[i].Size = new System.Drawing.Size(230, 300);
DataPoint d = new DataPoint();
d.XValue = 1;
double[] p = { (double)drives[i].TotalFreeSpace / 1000000000 };
d.YValues = p;
d.Color = System.Drawing.Color.YellowGreen;
d.Label = "Free Space";
charts[i].Series[0].Points.Add(d);
d.Label = "Used Space";
d.XValue = 2;
double[] a = { (double)((drives[i].TotalSize - drives[i].TotalFreeSpace) / 1000000000) };
d.YValues = a;
d.Color = System.Drawing.Color.Red;
charts[i].Series[0].Points.Add(d);
Form1.tabs.TabPages[1].Controls.Add(charts[i]);
charts[i].Invalidate();
}
}
Thanks.
You are almost there.
But the most basic thing you need to add to a dynamically created chart..:
charts[i] = new Chart();
..is a ChartArea:
charts[i].ChartAreas.Add("CA1"); // pick your name!
Without it no Series can display..
Use it to style the axis with TickMarks, GridLines or Labels or to set Minima and Maxima and Intervals. Well, at least for most other ChartTypes; Pies don't need any of this anyway..
Note that you can have several ChartAreas in one Chart.
Also note that it still will display nothing until at least one Series has at least one DataPoint..

Displaying pictureBox array

I would like to display 13 pictureBox, however, it ends up with only the last one visible.
So I was wondering if I did it in a wrong way.
The following code get image from resources folder.
var testP = new PictureBox();
for (int i = 0; i < 13; i++)
{
testP.Width = 65;
testP.Height = 80;
testP.BorderStyle = BorderStyle.None;
testP.SizeMode = PictureBoxSizeMode.StretchImage;
test[i] = getImage(testP, testPTemp[i]);
}
The following code is trying to display 13 pictureBox with shifting location.
These two codes segments should be able to perform the action.
test = new PictureBox[13];
for (var i = 0; i < 13; i++)
{
test[i].Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + testTemp[i]);
test[i].Left = 330;
test[i].Top = 500;
test[i].Location = new Point(test[i].Location.X + 0 * displayShift, test[i].Location.Y);
this.Controls.Add(test[i]);
}
Here is the getImage()
private PictureBox getImage(PictureBox pB, string i) // Get image based on the for loop number (i)
{
pB.Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + i); // Get the embedded image
pB.SizeMode = PictureBoxSizeMode.StretchImage;
return pB;
}
I'm pretty sure there are all PictureBox Controls but they have all the same location so they are lying above each other. That's why only the last one is visible to you.
I think you should replace the 0 with the i variable.
test[i].Location = new Point(test[i].Location.X + i * displayShift, test[i].Location.Y); this.Controls.Add(test[i]);
It's hard to tell the exact problem based off the code you've provided. One possible issue could be that when you are creating the PictureBoxes you only create a single instance before the for loop and then fill the array with references to that instance. Another possibility is that when you're calculating the X position of the controls, you're multiplying by 0 which will always result in 0 (meaning all the controls are at location 330).
Below is code that will achieve basically what you're trying but without all your code I can't give you a more specific example.
In Your Class
const int PICTURE_WIDTH = 65;
const int PICTURE_HEIGHT = 85;
Inside You Function
//Loop through each image
for(int i = 0; i < testTemp[i].length; i++)
{
//Create a picture box
PictureBox pictureBox = new PictureBox();
pictureBox.BorderStyle = BorderStyle.None;
pictureBox.SizeMode = PictureBoxSizeMode.StretchImage;
//Load the image date
pictureBox.Image = (Image)Properties.Resources.ResourceManager.GetObject("_" + testTemp[i]);
//Set it's size
pictureBox.Size = new Size(PICTURE_WIDTH, PICTURE_HEIGHT);
//Position the picture at (330,500) with a left offset of how many images we've gone through so far
pictureBox.Location = new Point(330 + (i * PICTURE_WIDTH), 500);
//Add the picture box to the list of controls
this.Controls.Add(pictureBox);
}
If you need to keep a list of the picture boxes, just create a new list before the loop and add each pictureBox to the list inside the loop. If the control/window you're adding these PictureBoxes to needs to scroll left or right to see all the images set the AutoScroll property to true.

Oxyplot annotation with brush

I wish to colour my OxyPlot's RectangleAnnotation with something more pretty than just a plain colour, i.e. some type of brush (GradientBrush etc.). I am working in WPF.
This question (PlotAreaBackground can be defined using gradient ?) on their GitHub site suggests that an annotation can be given a gradient, but I cannot find the suggested example.
Is there a way of applying a brush to a RectangleAnnotation? or any other way of creating coloured areas on a graph that are linked to the values on the axis?
I think that this is the suggested example.
I've modified it to look like a rectangle annotation, because the example fills the entire background. Hope it helps.
Efect:
Code:
// create a gradient image of height n
int n = 256;
var imageData1 = new OxyColor[n, 1];
for (int i = 0; i < n; i++)
{
imageData1[i, 0] = OxyColor.Interpolate(OxyColors.Red, OxyColors.Blue, i / (n - 1.0));
}
PngBitmapEncoder encoder = new PngBitmapEncoder();
PngEncoder encode = new PngEncoder(new PngEncoderOptions());
var image1 = new OxyImage(encode.Encode(imageData1));
ImageAnnotation anotation = new ImageAnnotation
{
ImageSource = image1,
Interpolate = true,
Layer = AnnotationLayer.BelowAxes,
X = new PlotLength(0.5, PlotLengthUnit.RelativeToPlotArea),
Y = new PlotLength(0.5, PlotLengthUnit.RelativeToPlotArea),
Width = new PlotLength(0.1, PlotLengthUnit.RelativeToPlotArea),
Height = new PlotLength(0.5, PlotLengthUnit.RelativeToPlotArea),
HorizontalAlignment = HorizontalAlignment.Left,
VerticalAlignment = VerticalAlignment.Top
};
plotModel.Annotations.Add(anotation);

Add Point to Canvas

I'm coding in Microsoft Visual Studio 2010 Express for Windows Phone. I need to add a point onto a Canvas, but I can't...
for (float x = x1; x < x2; x += dx)
{
Point poin = new Point();
poin.X = x;
poin.Y = Math.Sin(x);
canvas1.Children.Add(poin);
}
Studio says:
Error 2 Argument 1: cannot convert from 'System.Windows.Point' to 'System.Windows.UIElement'
My question is: how do I add a point onto a Canvas?
From your code snippet I assume you're trying to draw a curve. To do this, you can look into GraphicsPath. Instead of drawing individual points, you can use the points as coordinates, which you connect through lines. Then, in your code, you can create a GraphicsPath using the AddLine method.
This could then be drawn onto a bitmap, for example.
EDIT
Sample (not tested):
GraphicsPath p = new GraphicsPath();
for (float x = x1; x < x2; x += dx)
{
Point point = new Point();
point.X = x;
point.Y = Math.Sin(x);
Point point2 = new Point();
point2.X = x+dx;
point2.Y = Math.Sin(x+dx);
p.AddLine(point, point2);
}
graphics.DrawPath(p);
Another way would be to use the WPF Path class, which would work about the same, but is a real UI element which you can add to the children of a Canvas.
EDIT
People have pointed out that the above code is Windows Forms code. Well, here's what you can do in WPF:
myPolygon = new Polygon();
myPolygon.Stroke = System.Windows.Media.Brushes.Black;
myPolygon.Fill = System.Windows.Media.Brushes.LightSeaGreen;
myPolygon.StrokeThickness = 2;
myPolygon.HorizontalAlignment = HorizontalAlignment.Left;
myPolygon.VerticalAlignment = VerticalAlignment.Center;
PointCollection points = new PointCollection();
for (float x = x1; x < x2; x += dx)
{
Point p = new Point(x, Math.Sin(x));
points.Add(p);
}
myPolygon.Points = points;
canvas1.Children.Add(myPolygon);
If it is 'just a single point you want to add, you can add a tiny rectangle or ellipse to the canvas.
If you want to set a lot of points or a couple points many times, I suggest you create an array of pixel data (colors) and write those to a WriteableBitmap
The Point you used is not a UIElement but a struct, please use Line instead.
Line lne = new Line();
lne.X1 = 10;
lne.X2 = 11;
lne.Y1 = 10;
lne.Y2 = 10;
canvas1.Children.Add(lne);
You get the idea...
Edit
changed:
lne.X2 = 10 to lne.X2 = 11
As per the error, the children of the Canvas control must be derivatives of the System.Windows.UIElement class: System.Windows.Point is not. To achieve what you are doing, you would be best looking into using the geometry within WPF. See here for an article on how to do so.
Try adding a ellipse
Ellipse myEllipse = new Ellipse();
SolidColorBrush mySolidColorBrush = new SolidColorBrush();
mySolidColorBrush.Color = Color.FromArgb(255, 255, 255, 0);
myEllipse.Fill = mySolidColorBrush;
myEllipse.StrokeThickness = 2;
myEllipse.Stroke = Brushes.White;
myEllipse.Width = 200;
myEllipse.Height = 100;
Canvas.SetTop(myEllipse,50);
Canvas.SetLeft(myEllipse,80);
myCanvas.Children.Add(myEllipse);

Categories

Resources