Trouble drawing 3d-style effect - c#

I've been racking my brain trying to figure out how to animate an effect. This is related to a question I asked on math.stackexchange.com.
https://math.stackexchange.com/questions/91120/equal-division-of-rectangles-to-make-total/
As a side note, I didn't implement the drawing algorithm that was defined on the question above -- instead using my own in order to change the perspective to make it look more condensed.
I've been able to draw a stationary 3d style effect, but I am having trouble wrapping my brain around the logic to make the lines below look like they are coming towards you.
My code is as follows,
List<double> sizes = new List<double>();
private void Form1_Load(object sender, EventArgs e)
{
for (int y = 1; y < 10; y++)
{
double s = ((240 / 2) / y) / 4;
sizes.Add(s);
}
sizes.Add(0);
}
int offset = 0;
private void button1_Click(object sender, EventArgs e)
{
Bitmap b = new Bitmap(320, 480);
Graphics g = Graphics.FromImage(b);
Color firstColor = Color.DarkGray;
Color secondColor = Color.Gray;
Color c = firstColor;
int yOffset = 0;
for(int i = 0; i < sizes.Count; i++)
{
c = (i % 2 == 0) ? firstColor : secondColor;
int y = (int)Math.Round(b.Height - yOffset - sizes[i]);
int height = (int)Math.Round(sizes[i]);
g.FillRectangle(new SolidBrush(c), new Rectangle(0, y + offset, b.Width, height + offset));
yOffset += (int)sizes[i];
}
this.BackgroundImage = b;
offset+=1;
}
Each button click should cause the rectangles to resize and move closer. However, my rectangles aren't growing as they should. My logic draws fine, but simply doesn't work as far as moving goes.
So my question is:
Is there an existing algorithm for this effect that I am not aware of, or is this something pretty simple that I'm over thinking? Any help in correcting my logic or pointing me in the right direction would be very appreciated.

Interesting...
(video of the answer here: http://youtu.be/estq62yz7v0)
I would do it like that:
First, drop all RECTANGLE drawing and draw your effect line by line. Like so:
for (int y=start;y<end;y++)
{
color = DetermineColorFor(y-start);
DrawLine(left, y, right, y, color);
}
This is of course pseudo-code not to be troubled with GDI+ or something.
Everything is clear here, except on how to code DetermineColorFor() method. That method will have to return color of the line at specified PROJECTED height.
Now, on the picture, you have:
you point of view (X) - didn't know how to draw an eye
red line (that's your screen - projection plane)
your background (alternating stripes at the bottom)
and few projecting lines that should help you devise the DetermineColorFor() method
Hint - use triangle similarity to go from screen coordinates to 'bar' coordinates.
Next hint - when you are in 'bar' coordinates, use modulo operator to determine color.
I'll add more hints if needed, but it would be great if you solved this on your own.
I was somehow inspired by the question, and have created a code for the solution. Here it is:
int _offset = 0;
double period = 20.0;
private void timer1_Tick(object sender, EventArgs e)
{
for (int y = Height / 3; y < Height; y++)
{
using (Graphics g = CreateGraphics())
{
Pen p = new Pen(GetColorFor(y - Height / 3));
g.DrawLine(p, 0, y, Width, y);
p.Dispose();
}
}
_offset++;
}
private Color GetColorFor(int y)
{
double d = 10.0;
double h = 20.0;
double z = 0.0;
if (y != 0)
{
z = d * h / (double)y + _offset;
}
double l = 128 + 127 * Math.Sin(z * 2.0 * Math.PI / period);
return Color.FromArgb((int)l, (int)l, (int)l);
}
Experiment with:
d - distance from the eye to the projection screen
h - height of the eye from the 'bar'
period - stripe width on the 'bar'
I had a timer on the form and event properly hooked. Timer duration was 20ms.

Considering that you're talking here about 2D rendering, as much as I understodd, to me it seems that you're gonna to reenvent the wheel. Cause what you need, IMHO; is use Matrix Transformations already available in GDI+ for 2D rendering.
Example of aplying it in GDI+ : GDI+ and MatrixTranformations
For this they use System.Drawing.Drawing2D.Matrix class, which is inside Graphics.
The best ever 2D rendering framework I ever used is Piccolo2D framework which I used with great success in big real production project. Definitely use this for your 2D rendering projects, but first you need to study it little bit.
Hope this helps.

Related

Drawing zig-zag lines is much slower than drawing straight lines

While using a self-written graphing control I noticed that the painting of the graph was much slower while displaying noisy data than when it displayed clean data.
I dug further into and narrowed the problem down to its bare minimum difference: Drawing the same amount of lines with varying Y values versus drawing lines with the same Y value.
So for example I put together the following tests. I generate lists of points, one with random Y values, one with the same Y, and one with a Zig-Zag Y pattern.
private List<PointF> GenerateRandom(int n, int width, int height)
{
//Generate random pattern
Random rnd = new Random();
float stepwidth = Convert.ToSingle(width / n);
float mid = Convert.ToSingle(height / 2);
float lastx = 0;
float lasty = mid;
List<PointF> res = new List<PointF>();
res.Add(new PointF(lastx, lasty));
for (int i = 1; i <= n; i++)
{
var x = stepwidth * i;
var y = Convert.ToSingle(height * rnd.NextDouble());
res.Add(new PointF(x, y));
}
return res;
}
private List<PointF> GenerateUnity(int n, int width, int height)
{
//Generate points along a simple line
float stepwidth = Convert.ToSingle(width / n);
float mid = Convert.ToSingle(height / 2);
float lastx = 0;
float lasty = mid;
List<PointF> res = new List<PointF>();
res.Add(new PointF(lastx, lasty));
for (int i = 1; i <= n; i++)
{
var x = stepwidth * i;
var y = mid;
res.Add(new PointF(x, y));
}
return res;
}
private List<PointF> GenerateZigZag(int n, int width, int height)
{
//Generate an Up/Down List
float stepwidth = Convert.ToSingle(width / n);
float mid = Convert.ToSingle(height / 2);
float lastx = 0;
float lasty = mid;
List<PointF> res = new List<PointF>();
res.Add(new PointF(lastx, lasty));
var state = false;
for (int i = 1; i <= n; i++)
{
var x = stepwidth * i;
var y = mid - (state ? 50 : -50);
res.Add(new PointF(x, y));
state = !state;
}
return res;
}
I now draw each list of points a few times and compare how long it takes:
private void DoTheTest()
{
Bitmap bmp = new Bitmap(970, 512);
var random = GenerateRandom(2500, bmp.Width, bmp.Height).ToArray();
var unity = GenerateUnity(2500, bmp.Width, bmp.Height).ToArray();
var ZigZag = GenerateZigZag(2500, bmp.Width, bmp.Height).ToArray();
using (Graphics g = Graphics.FromImage(bmp))
{
var tUnity = BenchmarkDraw(g, 200, unity);
var tRandom = BenchmarkDraw(g, 200, random);
var tZigZag = BenchmarkDraw(g, 200, ZigZag);
MessageBox.Show(tUnity.ToString() + "\r\n" + tRandom.ToString() + "\r\n" + tZigZag.ToString());
}
}
private double BenchmarkDraw(Graphics g, int n, PointF[] Points)
{
var Times = new List<double>();
for (int i = 1; i <= n; i++)
{
g.Clear(Color.White);
System.DateTime d3 = DateTime.Now;
DrawLines(g, Points);
System.DateTime d4 = DateTime.Now;
Times.Add((d4 - d3).TotalMilliseconds);
}
return Times.Average();
}
private void DrawLines(Graphics g, PointF[] Points)
{
g.DrawLines(Pens.Black, Points);
}
I come up with the following durations per draw:
Straight Line: 0.095 ms
Zig-Zag Pattern: 3.24 ms
Random Pattern: 5.47 ms
So it seems to get progressively worse, the more change there is in the lines to be drawn, and that is also a real world effect I encountered in the control painting I mentioned in the beginning.
My questions are thus the following:
Why does it make a such a brutal difference, which lines are to be drawn?
How can I improve the drawing speed for the noisy data?
Three reasons come to mind:
Line Length : Depending on the actual numbers sloped lines may be longer by just a few pixels or a lot or even by some substantial factor. Looking at your code I suspect the latter..
Algorithm : Drawing sloped lines does take some algorithm to find the next pixels. Even fast drawing routines need to do some computations as opposed to vertical or horizontal lines, which run straight through the pixel arrays.
Anti-Aliasing : Unless you turn off anti-aliasing completely (with all the ugly consequences) the number of pixels to paint will also be around 2-3 times more as all those anti-aliasing pixels above and below the center lines must also be calculated and drawn. Not to forget calculating their colors!
The remedy for the latter part is obviously to turn off anti-aliasing, but the other problems are simply the way things are. So best don't worry and be happy about the speedy straight lines :-)
If you really have a lot of lines or your lines could be very long (a few time the size of the screen), or if you have a lot of almost 0 pixel line, you have to wrote code to reduce useless drawing of lines.
Well, here are some ideas:
If you write many lines at the same x, then you could replace those by a single line between min and max y at that x.
If your line goes way beyond the screen boundary, you should clip them.
If a line is completly outside of the visible area, you should skip it.
If a line have a 0 length, you should not write it.
If a line has a single pixel length, you should write only that pixel.
Obviously, the benefit depends a lot on how many lines you draw... And also the alternative might not give the exact same result...
In practice, it you draw a chart on a screen, then if you display only useful information, it should be pretty fast on modern hardware.
Well if you use style or colors, it might not be as trivial to optimize the displaying of the data.
Alternatively, they are some charting component that are optimized for display large data... The good one are generally expensive but it might still worth it. Often trials are available so you can get a good idea on how much you might increase the performance and then decide what to do.

Charting - Set grid above the graph

I'm working with the System.Windows.Forms.DataVisualization.Charting library of C# in Visual Studio.
Creating the graphs themselves is no problem, however, since I'm using SeriesChartType.StackedArea100 for my serieses (which always fills the vertical graph space 100%), the grid (X & Y) is completely covered by the graphs.
However, I want the X-grid to be above the graphs, so it's easier to see which point belongs to what.
Am I missing something obvious here?
Gridlines are always drawn under the DataPoints.
One option is to make the Colors of the DataPoints semi-transparent.
Here is an example:
chart1.ApplyPaletteColors(); // necessary to access the original colors
if (checkBox1.Checked)
{
foreach (Series s in chart1.Series) s.Color = Color.FromArgb(192, s.Color);
}
You can raise alpha to 224 and still see the lines.
Or you could owner-draw GridLines in one of the xxxPaint events; but that of course is a little more complicated. OK, a lot more..
The drawing itself is regular GDI+ drawing with DrawLine calls in two loops.
But to get the loops and the coordinates right you need to :
Make sure you know/control the Minimum, Maximum & Interval for the axes. If they are not set but still on their auto-values you need to find a way to get at them.
know the Rectangle of the InnerPlotPosition in pixels(!). See here for two functions that will help you !
Here is an example:
private void chart1_PostPaint(object sender, ChartPaintEventArgs e)
{
if (checkBox1.Checked) return;
ChartArea ca = chart1.ChartAreas[0];
RectangleF ipar = InnerPlotPositionClientRectangle(chart1, ca);
Axis ax = ca.AxisX;
Axis ay = ca.AxisY;
Color gc = ax.MajorGrid.LineColor;
Pen pen = new Pen(gc);
double ix = ax.Interval == 0 ? 1 : ax.Interval; // best make sure to set..
double iy = ay.Interval == 0 ? 50 : ay.Interval; // ..the intervals!
for (double vx = ax.Minimum; vx <= ax.Maximum; vx+= ix)
{
int x = (int)ax.ValueToPixelPosition(vx) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, x, ipar.Top, x, ipar.Bottom);
}
for (double vy = ay.Minimum; vy <= ay.Maximum; vy += iy)
{
int y = (int)ay.ValueToPixelPosition(vy) + 1;
e.ChartGraphics.Graphics.DrawLine(pen, ipar.Left, y, ipar.Right, y);
}
pen.Dispose();
}
You should disable the GridLines and maybe even make the the Axes transparent:
chart1.ChartAreas[0].AxisX.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisY.MajorGrid.Enabled = false;
chart1.ChartAreas[0].AxisX.LineColor = Color.Transparent;
chart1.ChartAreas[0].AxisY.LineColor = Color.Transparent;

Text drawing "bold"

So I'm writing a program that generates a chart and saves it to PNG. From what I've read, if I were drawing to a window, it doesn't behave this way, but I'm not doing that.
The problem is that when I pass the brush I use to draw the label to another method to do the drawing, sometimes the text comes out looking bold. Also, the Y coordinate seems to have something to do with it, since it happens on every other row of the chart I'm drawing. And it's not a nice bold, either, it's like a gritty, messy looking bold. Some people have suggested changing the text rendering hint to antialiased, and it solves the "bolding" problem, but it doesn't look as nice as ClearType.
Note that none of this happens if I do everything in one method without passing the brush around, which is the most puzzling part of this. Any ideas?
Here's some of the code:
// Draw the timeline.
int y = 0;
bool shadeRow = true;
foreach (TimelineRow row in timeline.chart)
{
int rowHeight = row.height + TimelineRow.ROW_GAP;
if (shadeRow)
{
g.FillRectangle(shadeBrush, 0, y, chartWidth, rowHeight);
}
// Draw name labels, guidelines, and timeline row.
g.DrawString(row.name, labelFont, labelBrush, PADDING, (int)Math.Ceiling(y + (float)PADDING / 2));
for (int i = 0; i < row.years.Length; i++)
{
int blockX = labelsWidth + i * TimelineRow.DEFAULT_HEIGHT;
g.DrawLine(i % 5 == 0 ? yearGridDark : yearGridLight, blockX, y, blockX, y + rowHeight);
}
DrawRow(row, g, labelsWidth, y + 8);
y += rowHeight;
shadeRow = !shadeRow;
}
// Draw the year labels
int x = labelsWidth;
for (int year = timeline.startYear; year <= timeline.endYear; year += 5)
{
string yearString = Convert.ToString(year);
int width = (int)g.MeasureString(yearString, labelFont).Width;
g.DrawString(yearString, labelFont, labelBrush, x - width / 2, y);
x += 5 * TimelineRow.DEFAULT_HEIGHT;
}
I've had similar issues with drawing strings. In my cases, clearing the image FIRST with the background color has fixed the problem.
Wow, that actually did it.
Use Graphics.Clear() to set the initial color:
Bitamp bmp = new Bitmap(...);
Graphics g = Graphics.FromImage(bmp);
g.Clear(Color.White);
// ... now draw with "g" ...

X,Y coordinates for WriteableBitmap and GestureListener_Tap do not match - Windows Phone 8

I am creating a hidden object game and am trying to mark the object when found with an eclipse. I've manually saved the top left and bottom right coordinates of each picture which were obtained via the GestureListener_Tap event.
The problem is when I tried to draw an eclipse bounded by the coordinates using this code
WriteableBitmapExtensions.DrawEllipse(writeableBmp, AnsX1, AnsY1, AnsX2, AnsY2, Colors.Red);
The location of the eclipse is always off to the top left. Marking the pixel locations using the following codes show that they are indeed located differently then what I would expect from the GestureListener_Tap.
writeableBmp.SetPixel(AnsX1, AnsY1, Colors.Red);
writeableBmp.SetPixel(AnsX2, AnsY2, Colors.Red);
My code for marking the location:
private void fadeOutAnimation_Ended(object sender, EventArgs e)
{
WriteableBitmap writeableBmp = new WriteableBitmap(bmpCurrent);
imgCat.Source = writeableBmp;
writeableBmp.GetBitmapContext();
WriteableBitmapExtensions.DrawEllipse(writeableBmp, AnsX1, AnsY1, AnsX2, AnsY2, Colors.Red);
writeableBmp.SetPixel(AnsX1, AnsY1, Colors.Red);
writeableBmp.SetPixel(AnsX2, AnsY2, Colors.Red);
// Present the WriteableBitmap
writeableBmp.Invalidate();
//Just some animation code
RadFadeAnimation fadeInAnimation = new RadFadeAnimation();
fadeInAnimation.StartOpacity = 0.2;
fadeInAnimation.EndOpacity = 1.0;
RadAnimationManager.Play(this.imgCat, fadeInAnimation);
}
What am I missing?
EDIT:
My answer below does not take into account the screen orientation changed. See my comment below the answer. How do you map pixel coordinates to image coordinate?
EDIT 2:
Found a correct solution. Updated my answer
From #PaulAnnetts comment I managed to transform the pixel coordinates. My inital mistake was to assume the image coordinates are the same as the pixel coordinates! I use the following code to convert.
private int xCoordinateToPixel(int coordinate)
{
double x;
x = writeableBmp.PixelWidth / imgCat.ActualWidth * coordinate;
return Convert.ToInt32(x);
}
private int yCoordinateToPixel(int coordinate)
{
double y;
y = writeableBmp.PixelHeight / imgCat.ActualHeight * coordinate;
return Convert.ToInt32(y);
}
EDIT:
Since PixelHeight and PixelWidth is fixed, and ActualHeight & ActualWidth isn't, I should convert pixel to coordinate in the GestureListerner_Tap event.
if ((X >= xPixelToCoordinate(AnsX1) && Y >= yPixelToCoordinate(AnsY1)) && (X <= xPixelToCoordinate(AnsX2) && Y <= yPixelToCoordinate(AnsY2)))
{...}
And my pixel to coordinate converters
private int xPixelToCoordinate(int xpixel)
{
double x = imgCat.ActualWidth / writeableBmp.PixelWidth * xpixel;
return Convert.ToInt32(x);
}
private int yPixelToCoordinate(int ypixel)
{
double y = imgCat.ActualHeight / writeableBmp.PixelHeight * ypixel;
return Convert.ToInt32(y);
}

Faster method for drawing in C#

I'm trying to draw the Mandelbrot fractal, using the following method that I wrote:
public void Mendelbrot(int MAX_Iterations)
{
int iterations = 0;
for (float x = -2; x <= 2; x += 0.001f)
{
for (float y = -2; y <= 2; y += 0.001f)
{
Graphics gpr = panel.CreateGraphics();
//System.Numerics
Complex C = new Complex(x, y);
Complex Z = new Complex(0, 0);
for (iterations = 0; iterations < MAX_Iterations && Complex.Abs(Z) < 2; Iterations++)
Z = Complex.Pow(Z, 2) + C;
//ARGB color based on Iterations
int r = (iterations % 32) * 7;
int g = (iterations % 16) * 14;
int b = (iterations % 128) * 2;
int a = 255;
Color c = Color.FromArgb(a,r,g,b);
Pen p = new Pen(c);
//Tranform the coordinates x(real number) and y(immaginary number)
//of the Gauss graph in x and y of the Cartesian graph
float X = (panel.Width * (x + 2)) / 4;
float Y = (panel.Height * (y + 2)) / 4;
//Draw a single pixel using a Rectangle
gpr.DrawRectangle(p, X, Y, 1, 1);
}
}
}
It works, but it's slow, because I need to add the possibility of zooming. Using this method of drawing it isn't possible, so I need something fast. I tried to use a FastBitmap, but it isn't enough, the SetPixel of the FastBitmap doesn't increase the speed of drawing. So I'm searching for something very fast, I know that C# isn't like C and ASM, but it would be interesting do this in C# and Winforms.
Suggestions are welcome.
EDIT: Mendelbrot Set Zoom Animation
I assume it would be significantly more efficient to first populate your RGB values into a byte array in memory, then write them in bulk into a Bitmap using LockBits and Marshal.Copy (follow the link for an example), and finally draw the bitmap using Graphics.DrawImage.
You need to understand some essential concepts, such as stride and image formats, before you can get this to work.
As comment said put out CreateGraphics() out of the double loop, and this is already a good imrovement.
But also
Enable double buffering
For zooming use MatrixTransformation functions like:
ScaleTransform
RotateTransform
TranslateTransform
An interesting article on CodeProject can be found here. It goes a little bit further than just function calls, by explaining actually Matrix calculus ( a simple way, don't worry), which is good and not difficult to understand, in order to know what is going on behind the scenes.

Categories