I'm trying to draw rectangles into a WriteableBitmap, unfortunatelly the WriteableBitmapEx that provides Fill* extensions are too slow and can be run only at the main thread.
I'm looking for alternatives specific for WP8.1 and don't know the best solution so far.
I need a way to draw the rectangles async, one approach was creating a Canvas at the MainWindow and adding xaml.Rectangles on it, this almost can be used as solution for the problem, but I want specific draw the rectangles on the WriteableBitmap instead of creating a ton of UIElements and adding all of then on the screen.
Sorry if any given solution can be found on internet, I can't find almost nothing about C#.
A test I did:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var container = new Canvas()
{
Width = 300,
Height = 500
};
var winImage = new Image()
{
Width = 300,
Height = 500
};
container.Children.Add(winImage);
//var winImage = imageView.NativeView<Image>();
var img = new WriteableBitmap((int)winImage.Width, (int)winImage.Height);
var clr = Color.FromArgb(255, 0, 0, 255);
var start = DateTime.Now;
var random = new Random();
for (int i = 0; i < 50; i++)
{
//var color = Color.FromArgb(255, (byte)random.Next(255), (byte)random.Next(255), (byte)random.Next(255));
img.FillRectangle(i * 2, i * 2, i * 2 + 10, i * 2 + 10, clr);
}
Debug.WriteLine((DateTime.Now - start).TotalMilliseconds + "ms drawing");
winImage.Source = img;
Content = container;
}
This results in "792.1397ms drawing" running on debug mode on a Nokia Lumia 1020, that is pretty slow.
Using GetBitmapContext() should make it a lot faster.
protected override void OnNavigatedTo(NavigationEventArgs e)
{
var container = new Canvas()
{
Width = 300,
Height = 500
};
var winImage = new Image()
{
Width = 300,
Height = 500
};
container.Children.Add(winImage);
var img = BitmapFactory.New((int)winImage.Width, (int)winImage.Height);
winImage.Source = img;
Content = container;
var clr = Color.FromArgb(255, 0, 0, 255);
var random = new Random();
var sw = new Stopwatch();
sw.Start();
using (img.GetBitmapContext()) {
img.Clear(Colors.White);
for (var x = 0; x < 10; x++) {
for (var y = 0; y < 10; y++) {
img.FillRectangle(x * 10, y * 10, x * 10 + 10, y * 10 + 10, clr);
}
}
}
sw.Stop();
Debug.WriteLine(sw.ElapsedMilliseconds + "ms drawing");
}
Related
I am unable to get the ScrollBars to appear for a Canvas when its children are out of view. This is being directly added to the MainWindow.
<ScrollViewer>
<Canvas x:Name="MainCanvas"/>
</ScrollViewer>
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
for (int i = 0; i < 20; i++)
{
var rect = new Rectangle()
{
Height = 100,
Width = 200,
Stroke = new SolidColorBrush(Colors.Magenta)
};
Canvas.SetLeft(rect, (i * 200) + 10);
Canvas.SetTop(rect, 10);
var rect1 = new Rectangle()
{
Height = 100,
Width = 200,
Stroke = new SolidColorBrush(Colors.DarkOrchid)
};
Canvas.SetLeft(rect1, 10);
Canvas.SetTop(rect1, (i * 100) + 10);
MainCanvas.Children.Add(rect);
MainCanvas.Children.Add(rect1);
}
}
So, children are being added that go out of view horizontally and vertically. I've tried various settings for scrollbar visibility, alignments, etc. but just cant get the scrollbars to appear. Thanks for any inputs.
Specify a size for the Canvas so the ScrollViewer can measure it:
private void MainWindow_Activated(object sender, WindowActivatedEventArgs args)
{
MainCanvas.Width = 0;
MainCanvas.Height = 0;
for (int i = 0; i < 20; i++)
{
var rect = new Rectangle()
{
Height = 100,
Width = 200,
Stroke = new SolidColorBrush(Colors.Magenta)
};
double left = (i * 200) + 10;
MainCanvas.Width = left;
Canvas.SetLeft(rect, left);
Canvas.SetTop(rect, 10);
var rect1 = new Rectangle()
{
Height = 100,
Width = 200,
Stroke = new SolidColorBrush(Colors.DarkOrchid)
};
double top = (i * 100) + 10;
MainCanvas.Height = top;
Canvas.SetLeft(rect1, 10);
Canvas.SetTop(rect1, top);
MainCanvas.Children.Add(rect);
MainCanvas.Children.Add(rect1);
}
}
You may also want to enable horizontal scrolling:
<ScrollViewer HorizontalScrollBarVisibility="Auto">
...
I have created a program to draw square grids on a selected image. It works fine for images that has small resolution, but it doesn't work properly on large images.
The all grid lines are not visible seem when the image is saved as file.
The image I am testing has resolution 3600x4320 and can be shown in the link.
How can I fix this problem?
My code:
Image drawGrid(int n, string imgPath)
{
Image img = Image.FromFile(imgPath);
Graphics grp = Graphics.FromImage(img);
grp.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
float m = img.Width * 1f / n;
for (int i = 1; i < n; i++)
{
var x = new PointF(i * m, 0);
var y = new PointF(i * m, img.Height);
grp.DrawLine(Pens.Red, x, y);
}
for (int i = 1; i <= (int)(this.Height / m); i++)
{
var x = new PointF(0, i * m);
var y = new PointF(img.Width, i * m);
grp.DrawLine(new Pen(Color.Red, 5f), x, y);
}
return img;
}
void BtnExportClick(object sender, EventArgs e)
{
if(saveFileDialog1.ShowDialog() == DialogResult.OK)
{
int n = (int)numericUpDown1.Value;
drawGrid(n, txtImagePath.Text).Save(saveFileDialog1.FileName, System.Drawing.Imaging.ImageFormat.Jpeg);
MessageBox.Show("Done");
}
}
The result image is below (resolution reduced to upload)
The grid lines not shown correctly.
The major problem is in this line:
for (int i = 1; i <= (int)(this.Height / m); i++)
▶ this.Height is clearly not what you wanted to write, let's replace it with the Image.Height
▶ grp.DrawLine(Pens.Red, x, y); and grp.DrawLine(new Pen(Color.Red, 5f), x, y); will draw lines of different size (1 and 5 pixels). In the sample code, the two methods accept Color and float arguments that define the Pen color and size.
▶ grp.SmoothingMode: we don't want any smoothing mode here, not needed to draw straight lines and it will add anti-alias which will be clearly visible, especially when saving the Image in JPEG format (it will anti-alias - sort of, it actually mangles the colors - these lines by itself).
▶ You're not disposing of any of the Graphics object you create. This is quite important with both frequent graphics operations and when working with large Bitmaps.
The Pen and Graphics objects needs to be disposed.
Since it's not exactly clear if you want to generate a grid that has the same number of lines in both dimensions - hence, a grid with Cells in which the Width is not equal to the Height, most probably - or a grid with squared Cells (this is what the sample Image seems to show, not the code), I posted two method that draw both grid types:
First method, same number of lines in both width and height:
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
private Image DrawGridLines(int lines, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSizeX = (float)image.Width / lines;
var gridSizeY = (float)image.Height / lines;
for (int i = 1; i < lines; i++) {
var pointX1 = new PointF(0, i * gridSizeY);
var pointX2 = new PointF(image.Width, i * gridSizeY);
var pointY1 = new PointF(i * gridSizeX, 0);
var pointY2 = new PointF(i * gridSizeX, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
g.DrawLine(pen, pointY1, pointY2);
}
}
return image;
}
}
Second method, drawing a squared grid. The integer value, gridSection, is used to define a grid Cell based on the minimum dimension of the Bitmap.
This dimension is then used to determine how many lines to draw in the other dimension.
The grid size is calculated on the minimum dimension:
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
And the Cell are determined as a consequence:
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
private Image DrawSquaredGrid(int gridSection, string imgPath, Color penColor, float penSize)
{
var image = Image.FromStream(new MemoryStream(File.ReadAllBytes(imgPath)), true);
using (var g = Graphics.FromImage(image)) {
g.PixelOffsetMode = PixelOffsetMode.Half;
var gridSize = (float)Math.Min(image.Width, image.Height) / gridSection;
var gridStepMin = Math.Min(image.Width, image.Height) / gridSize;
var gridStepMax = Math.Max(image.Width, image.Height) / gridSize;
for (int i = 1; i < gridStepMin; i++) {
var pointY1 = new PointF(i * gridSize, 0);
var pointY2 = new PointF(i * gridSize, image.Height);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointY1, pointY2);
}
}
for (int i = 1; i < gridStepMax; i++) {
var pointX1 = new PointF(0, i * gridSize);
var pointX2 = new PointF(image.Width, i * gridSize);
using (var pen = new Pen(penColor, penSize)) {
g.DrawLine(pen, pointX1, pointX2);
}
}
return image;
}
}
The SaveFileDialog is refactored to allow multiple Image formats. and to call one of the drawing methods based on a selection (in the sample code, a CheckBox (chkSquared) is used select one of the Grid types).
You can add more formats, the ImageFormatFromFileName() methods selects the ImageFormat type based on the SaveFileDialog.FielName extension.
private void BtnExportClick(object sender, EventArgs e)
{
string imagePath = [Some Path];
using (var sfd = new SaveFileDialog()) {
sfd.Filter = "PNG Image (*.png)|*.png|TIFF Image (*.tif)|*.tif|JPEG Image (*.jpg)|*.jpg";
sfd.RestoreDirectory = true;
sfd.AddExtension = true;
if (sfd.ShowDialog() == DialogResult.OK) {
Image image = null;
if (chkSquared.Checked) {
image = DrawSquaredGrid((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
else {
image = DrawGridLines((int)numericUpDown1.Value, imagePath, Color.Red, 5.0f);
}
image.Save(sfd.FileName, ImageFormatFromFileName(sfd.FileName));
MessageBox.Show("Done");
image.Dispose();
}
}
}
private ImageFormat ImageFormatFromFileName(string fileName)
{
string fileType = Path.GetExtension(fileName).Remove(0, 1);
if (fileType.Equals("tif")) fileType = "tiff";
if (fileType.Equals("jpg")) fileType = "jpeg";
return (ImageFormat)new ImageFormatConverter().ConvertFromString(fileType);
}
My goal is to have a robot use this grid to create a map base off the information it collects from its surroundings. When the robot detects an object the square in front of it turns red. Currently I am stuck on how I can give each square an x and Y value for location purposes. Also when I scroll the screen the block sizes change, can someone provide help with that as well?
Rectangle rect = new Rectangle(700, 350, 50, 50);
g.DrawRectangle(myPen, rect); // Draws the Rectangle to the screen
e.Graphics.FillEllipse(myBrush, 700,350,50,50);`
for (int i = 0; i < 9900; i = i + 50)
{
rect = new Rectangle(0 + i, 0, 50, 50);
g.DrawRectangle(myPen, rect);
for (int j = 0; j < 9900; j = j + 50)
{
rect = new Rectangle(0 + i, 0 + j, 50, 50);
g.DrawRectangle(myPen, rect);
}
}
Here is a very quick example of how to do this using a 2d array. It was written in LINQPad, so it may look a little odd, but it should give you some leads. It allows you to store a map and look up values using x and y coordinates. You can use the CellInfo class to add any extra information about the cell that you need, beyond if it is blocking or not.
Ideally, you would want to wrap the entire array up in your own Map class, that abstracts away the details, and gives you a lot of helpful utility functions. For instance, if your map is extremely large, you may run out of memory. You could have the Map class only load smaller blocks of the map from files on disk as needed, or even make the map wrap around its self easily.
void Main()
{
var map = new CellInfo[10, 10];
for (int x = 0; x < 10; x++)
{
for (int y = 0; y < 10; y++)
{
map[x, y] = new CellInfo();
}
}
var rnd = new Random();
for (int i = 0; i < 20; i++)
{
map[rnd.Next(0, 10), rnd.Next(0, 10)].IsBlocked = true;
}
DrawMap(map).Dump();
}
public Bitmap DrawMap(CellInfo[,] map)
{
var img = new Bitmap(320, 320, PixelFormat.Format32bppArgb);
using (var g = Graphics.FromImage(img))
{
for (int x = 0; x < 10; x++)
{
for (int y = 0; y < 10; y++)
{
var cell = map[x, y];
Brush brush = cell.IsBlocked ? Brushes.Red : Brushes.White;
g.FillRectangle(brush, x * 32, y * 32, 31, 31);
g.DrawRectangle(Pens.Black, x * 32, y * 32, 31, 31);
}
}
}
return img;
}
public class CellInfo
{
public bool IsBlocked { get; set; } = false;
}
It produces the following output (varies each time it is run):
I don't understand something. If i don't use the customlabels, the chart will use the default label. And then if I move the scrollbar , the chart size won't adjust. The Chart view maintain the original size.
But if I use this code to change the label at row 0. (other rows don't have this problem)
chart1.ChartAreas[0].AxisY2.CustomLabels.Add((i) ,
(i+1), (ntemp * 10).ToString(), 0, LabelMarkStyle.SideMark);
And Move the scrollbar, the chart View will be a little different for size. The chart will flicker, and I don't want it.
Thanks in advance.
Here is example
Random rand = new Random();
chart1.Series.Clear();
var series = chart1.Series.Add("My Series");
series.ChartType = SeriesChartType.RangeBar;
series.Color = Color.Black;
series.YAxisType = AxisType.Secondary;
for (int i = 10; i > 2; i--)
series.Points.AddXY(i, (rand.Next(3600, 7200)), (rand.Next(30000, 80000)));
var chartArea = chart1.ChartAreas[series.ChartArea];
chartArea.BorderDashStyle = ChartDashStyle.Solid; //最外圍的框框
chartArea.BorderWidth = 10;
chartArea.AxisY.Enabled = AxisEnabled.False;
chartArea.AxisY2.Enabled = AxisEnabled.True;
chartArea.AxisY2.LabelStyle.IntervalType = DateTimeIntervalType.Number;
chartArea.AxisY2.Interval = 3600;
chartArea.AxisY2.Minimum = 0;
chartArea.AxisY2.Maximum = 86400;
chartArea.AxisY2.ScaleView.Zoom(0, 3600 * 4);
for (int i = 0; i <= 24 * 6; i++)
{
int ntemp = i % 6;
if (ntemp != 0)
{
/*Problem Here !!*/
//chart1.ChartAreas[0].AxisY2.CustomLabels.Add((i) * 600, (i + 1) * 600, (ntemp * 10).ToString(), 0, LabelMarkStyle.Box);
}
}
chartArea.CursorY.AutoScroll = true;
chartArea.AxisY2.ScaleView.Zoomable = true;
chartArea.AxisY2.ScrollBar.ButtonStyle = ScrollBarButtonStyles.SmallScroll;
chartArea.AxisY2.ScrollBar.IsPositionedInside = false;
}
Well, I was intrigued about how and if this can be achieved with OxyPlot, and I think it can ...
Here's the code I've used, and here's a screenshot:
var model = new PlotModel("IntervalBarSeries") { LegendPlacement = LegendPlacement.Outside };
var temp_serie = new IntervalBarSeries
{
Title = "IntervalBarSeries 1",
FillColor = OxyColors.Black
};
var categoryAxis = new CategoryAxis
{
Position = AxisPosition.Left,
IsZoomEnabled = false, // No zoom on this axis
IsPanEnabled = false, // Right mouse move won't affect this axis
MajorGridlineStyle = LineStyle.Solid
,StartPosition = 1, EndPosition = 0 // This will reverse the order
};
var valueAxis = new LinearAxis(AxisPosition.Top)
{
MinimumPadding = 0.1, MaximumPadding = 0.1,
IsZoomEnabled = true,
MajorGridlineStyle = LineStyle.Solid,
MajorStep = 3600,
AbsoluteMinimum = 0
};
for (int i = 10; i > 2; i--)
{
temp_serie.Items.Add(new IntervalBarItem {
Start = rand.Next(3600, 7200),
End = rand.Next(30000, 80000)
});
categoryAxis.Labels.Add("Activity "+i);
}
model.Series.Add(temp_serie);
model.Axes.Add(categoryAxis);
model.Axes.Add(valueAxis);
MyPlotModel = model;
Now, I'm using MVVM and just binding to the plot model from my View with:
<oxy:Plot Model="{Binding MyPlotModel}"/>
But you can figure out how to do the same with WinForms once (if?) you decide to use OxyPlot and import it.
I'm assuming you're doing some work that is related to times, but your code obviously doesn't say so ... you could play around with the top header, and maybe set how to show the numbers (ATM, with no zoom, they overlap each other a bit. zooming with scroller solves that, but that's just because i've set the tick size to 3600 ... )
The code below does not adding rectangle. Can anyone advice What i missed ? mainCanvas.Children.Add(rectangle[i]); does not work.
RoomX.Count = 5 how I can go with for add all five rectangles with different width and height which is in RoomX, RoomY
private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
HProDataContext db = new HProDataContext();
var RoomX = (from d in db.rooms select d.sizex).ToList();
var RoomY = (from d in db.rooms select d.sizey).ToList();
for (int i = 0; i < RoomX.Count; i++)
{
var random = new Random();
var rectangle = new Rectangle()
{
Stroke = Brushes.Black,
Fill = Brushes.SkyBlue,
Width = Convert.ToDouble(RoomX),
Height = Convert.ToDouble(RoomY),
Margin = new Thickness(left: random.NextDouble() * 300,
top: random.NextDouble() * 150,
right: 0,
bottom: 0),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};
mainCanvas.Children.Add(rectangle);
}
}
Width = Convert.ToDouble(RoomX),
Height = Convert.ToDouble(RoomY),
Are you sure that's what you want to do? You're converting a list to a double there, which seems kinda off.
I think you need something like:
Width = Convert.ToDouble(RoomX[i]),
Height = Convert.ToDouble(RoomY[i]),