I'm working on a winforms project where I need to draw tiled images. So far this is what I have:
Update method (hooked to a timer.tick event) :
int elapsedTime = Environment.TickCount - lastTick;
tempLevel.Update(elapsedTime);
Invalidate();
lbl_fps.Text = "FPS: " + FPSHandler.CalculateFrameRate();
lastTick = Environment.TickCount;
Paint method:
public void Paint(PaintEventArgs e)
{
GraphicManager.drawTiledImage(e.Graphics, new Rectangle(0,0,level.Length,1024), "paper");
for (int i = 1; i < 800; i ++)
{
GraphicManager.drawLine(e.Graphics,
new PointF(i + GraphicManager.winpos.x - 1, level[(int)(i + GraphicManager.winpos.x - 1)] + 400),
new PointF(i + GraphicManager.winpos.x, level[(int)(i + GraphicManager.winpos.x)] + 400));
}
}
drawTiledImage method:
ImageAttributes imgA = new ImageAttributes();
imgA.SetWrapMode(WrapMode.Tile);
g.DrawImage(images[image],
ExtraMath.fromRect(
new Rectangle(
(int)Math.Round(rect.X - winPos.x),
(int)Math.Round(rect.Y - winPos.y),
rect.Width, rect.Height)),
new Rectangle(0, 0, rect.Width, rect.Height),
GraphicsUnit.Pixel, imgA);
winpos is a vector, a custom class similar to PointF, except with more features, and uses floating point numbers to store x/y, and is convertible to floats, Points, and PointFs.
When I run this code, it appears to work until I begin to scroll down, and the screen looks like this:
it should look like this (minus the line):
Any help?
Extra info:
- the winPos is basically a wrapped version of PointF that has more operators and such
- The glitch appears very aggressively whenever winpos.y approaches the height of the image
- The glitch does not happen when scrolling to the left/right
Related
I'm drawing coordinate axes in picturebox
void draw_cor()
{
int w = pictureBox1.ClientSize.Width / 2;
int h = pictureBox1.ClientSize.Height / 2;
Refresh();
Graphics e = pictureBox1.CreateGraphics();
e.TranslateTransform(w, h);
DrawXAxis(new Point(-w, 0), new Point(w, 0), e);
DrawYAxis(new Point(0, h), new Point(0, -h), e);
DrawZAxis(new Point(-pictureBox1.ClientSize.Width , pictureBox1.ClientSize.Height), new Point(pictureBox1.ClientSize.Width, -pictureBox1.ClientSize.Height ), e);
}
markup and text for the x axis as an example
private void DrawXAxis(Point start, Point end, Graphics g)
{
for (int i = Step; i < end.X; i += Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
for (int i = -Step; i > start.X; i -= Step)
{
g.DrawLine(Pens.Black, i, -5, i, 5);
DrawText(new Point(i, 5), (i / Step).ToString(), g, false);
}
g.DrawLine(Pens.Black, start, end);
g.DrawString("X", new Font(Font.FontFamily, 10, FontStyle.Bold), Brushes.Black, new Point(end.X - 15, end.Y));
}
private void DrawText(Point point, string text, Graphics g, bool isYAxis)
{
var f = new Font(Font.FontFamily, 6);
var size = g.MeasureString(text, f);
var pt = isYAxis
? new PointF(point.X + 1, point.Y - size.Height / 2)
: new PointF(point.X - size.Width / 2, point.Y + 1);
var rect = new RectangleF(pt, size);
g.DrawString(text, f, Brushes.Black, rect);
}
can someone explain how to make a method for marking the z axis?
I understand that the shift should be diagonal in both x and y, but nothing worked out for me and no markup appears on the screen.(so far I have managed to draw only a straight line diagonally )
upd:
private void DrawZAxis(Point start, Point end, Graphics g)
{
for (int i = -Step, j=Step ; i > start.X; i -= Step,j += Step)
{
g.DrawLine(Pens.Black, new Point(i-5, j), new Point(i+5, j));
DrawText(new Point(i, j), (i / -Step).ToString(), g, false);
}
...
}
I seem to have succeeded, but I ran into such a problem:
that is, the markup is not always on the coordinate axis. How to avoid this? It is necessary that the numbers are always on the axis (I suppose I should calculate the coefficient when the window is scaled, but only where to add it or by what to multiply?)
You are dealing with 3D data, so you should use 3D tools to transform your axes, and your data for that matter.
So you need to define a projection from 3D space to 2D space. This is usually done by defining a projection matrix. There are multiple projections to chose from, it looks like your projection is Oblique, but orthographic and perspective projections are also common. The System.Numerics.Vectors library has classes for Matrix4x4, vector2/3/4, with methods to create your projection and transform your vectors.
After transforming a vector you can simply keep the x/y values and discard the z-value to get your image coordinates. Note that if using a perspective transform you need a vector4 and divide the x/y/z elements by W.
Armed with these tools it should be a fairly simple thing to generate start/end points for each axis, and create tick-marks in 3D, before projecting everything to 2D for drawing.
Another option would be to just do everything in Wpf3D to start with, this will likely make some functionality like rotating the camera simpler.
I know this question has been asked frequently, but none of the typical answers have given me the result I need. I am attempting to zoom a grayscale bitmap much like Paint.exe. I want no interpolation so the original, individual pixels can be observed. I have tried the oft-suggested NearestNeighbor approach which gets close, but not exactly what I want.
This is what I want:
This is what I get:
This is the code I am using to zoom and redraw the image.
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.PixelOffsetMode = PixelOffsetMode.Half;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.SmoothingMode = SmoothingMode.None;
Matrix m = new Matrix();
m.Scale(mScale, mScale, MatrixOrder.Append);
e.Graphics.Transform = m;
e.Graphics.TranslateTransform(this.AutoScrollPosition.X / mScale,this.AutoScrollPosition.Y / mScale);
if (mImage != null)
e.Graphics.DrawImage(mImage, 0, 0);
base.OnPaint(e);
}
The code does have an affect on the image as the zoom works and changing the InterpolationMode does change the image. However, no combination of settings gets the result I need.
Any ideas?
I'm trying to display 16-bit images with C#. I came across the same trouble that the colors in the totally same pixel is not the same. Finally I solved this trouble with the sample code below:
protected override void OnPaint(PaintEventArgs e)
{
e.Graphics.CompositingQuality = System.Drawing.Drawing2D.CompositingQuality.HighQuality;
e.Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.NearestNeighbor;
e.Graphics.PixelOffsetMode = System.Drawing.Drawing2D.PixelOffsetMode.HighSpeed;
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
e.Graphics.Clear(Color.Black); //Clear the background with the black color
if(m_bmp != null) //if m_bmp == null, then do nothing.
{
//To calculate the proper display area's width and height that fit the window.
if (this.Width / (double)this.Height > m_bmp.Width / (double)m_bmp.Height)
{
m_draw_height = (int)(this.Height * m_roomRatio);
m_draw_width = (int)(m_bmp.Width / (double)m_bmp.Height * m_draw_height);
}
else
{
m_draw_width = (int)(this.Width * m_roomRatio);
m_draw_height = (int)(m_bmp.Height / (double)m_bmp.Width * m_draw_width);
}
//To calculate the starting point.
m_draw_x = (int)((this.Width - m_draw_width) / 2.0 + m_offsetX / 2.0);
m_draw_y = (int)((this.Height - m_draw_height) / 2.0 + m_offsetY / 2.0);
e.Graphics.DrawImage(m_bmp, m_draw_x, m_draw_y, m_draw_width, m_draw_height);
//draw some useful information
string window_info = "m_draw_x" + m_draw_x.ToString() + "m_draw_width" + m_draw_width.ToString();
e.Graphics.DrawString(window_info, this.Font, new SolidBrush(Color.Yellow), 0, 20);
}
BTW, why don't you try to use double buffer to increase the performance of drawing images?
Here is the effect:
Hope that will help.
As title said:
I have form with 2 trackbars. One for frequency and one for amplitude. I set up timer for on-the-fly changing.
private void timer1_Tick(object sender, EventArgs e)
{
float amplitude, frequency;
amplitude = Convert.ToSingle(trackBar1.Value) / 100;
label1.Text = amplitude.ToString() + " V";
frequency = trackBar2.Value;
label2.Text = frequency.ToString() + " Hz";
}
I have also 4 radio-buttons to decide, which type of signal will be displayed (sine, square, triangle, sawthoot)
Now I have this implemented with ImageList (change image of signal).
How can I draw type of signal and regulate it with with trackbars? So it will be like in osciloscope.
Thanks for your answers and code.
Lets start by creating the different signal types, this is a function that creates one wavelength of amplitude 1:
private PointF[] CreateBaseSignal(SignalType signalType)
{
switch (signalType)
{
case SignalType.Sine:
const int oversampling = 32;
PointF[] signal = new PointF[oversampling];
for (int i = 0; i < signal.Length; i++)
{
signal[i].X = (float) i / oversampling;
signal[i].Y = Convert.ToSingle(Math.Sin((double) i / oversampling * 2 * Math.PI));
}
return signal;
case SignalType.Square:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, -1.0f),
new PointF(0.5f, 1.0f),
new PointF(1.0f, 1.0f),
};
case SignalType.Triangle:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, 1.0f),
};
case SignalType.Sawtooth:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(1.0f, 1.0f),
};
default:
throw new ArgumentException("Invalid signal type", "signalType");
}
}
Then we create the actual signal with the selected amplitude and frequency:
private PointF[] CreateSignal(PointF[] baseSignal, float frequency, float amplitude)
{
PointF[] signal = new PointF[Convert.ToInt32(Math.Ceiling(baseSignal.Length * frequency))];
for(int i = 0; i < signal.Length; i++)
{
signal[i].X = baseSignal[i % baseSignal.Length].X / frequency + (i / baseSignal.Length) / frequency;
signal[i].Y = baseSignal[i % baseSignal.Length].Y * amplitude;
}
return signal;
}
Before attempting to plot this signal to a PictureBox, we scale the signal to fit the width and height:
private PointF[] ScaleSignal(PointF[] signal, int width, int height)
{
const float maximumAmplitude = 10.0f;
PointF[] scaledSignal = new PointF[signal.Length];
for(int i = 0; i < signal.Length; i++)
{
scaledSignal[i].X = signal[i].X * width;
scaledSignal[i].Y = signal[i].Y * height / 2 / maximumAmplitude;
}
return scaledSignal;
}
Using Graphics.DrawLine to plot the signal is way better than Bitmap.SetPixel, since the data points will be connected even at high frequencies. Bitmap.SetPixel is also very slow, you really need to use Bitmap.LockBits and unsafe code for manipulating single pixels to achieve any decent performance. Using Graphics.DrawLine, you also have control over line width, anti-aliasing etc.
Since we have stored the signal in a PointF array, we can use the simple Graphics.DrawLines method to plot the signal instead of iterating over the data points:
private void PlotSignal(PointF[] signal, PictureBox pictureBox)
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
signal = ScaleSignal(signal, bmp.Width, bmp.Height); // Scale signal to fit image
using(Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.TranslateTransform(0, bmp.Height / 2); // Move Y=0 to center of image
gfx.ScaleTransform(1, -1); // Make positive Y axis point upward
gfx.DrawLine(Pens.Black, 0, 0, bmp.Width, 0); // Draw zero axis
gfx.DrawLines(Pens.Blue, signal); // Draw signal
}
// Make sure the bitmap is disposed the next time around
Image old = pictureBox.Image;
pictureBox.Image = bmp;
if(old != null)
old.Dispose();
}
If you redraw the signal often, you probably want to reuse the the Bitmap and Graphics objects instead of creating new ones each time. Just remember to call Graphics.Clear between each redraw.
Putting everything together in one big statement:
PlotSignal(
CreateSignal(
CreateBaseSignal(signalType),
frequency,
amplitude),
thePictureBox);
If you're after a fast plotting library, I really like Dynamic Data Display
Dynamic Data Display
This is a WPF component, but for fast, smooth drawing applications I really think it is worthwhile to port to WPF sooner rathar than later. It feels like you're not too far into your project at the moment anyway.
Development for WPF seems to have stopped for this component (although it continues to be worked on for Silverlight). The documentation is terrible but the source code is available from the link above so you can extend it as needed (it's quite well written and very extensible) and the source is invaluable as a substitute for the near complete lack of any documentation.
Assuming you want to plot a sin wave on a picture box control, create a picture box control on your form then perform the following:
int width = pictureBox1.Width;
int height = pictureBox1.Height;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, System.Drawing.Color.Red);
}
pictureBox1.Image = b;
Why does the paint even take so long?
public Form1()
{
InitializeComponent();
SuspendLayout();
double scale = ClientSize.Width / 11;
for (int i = 1; i < 10; i++)
{
for (int j = 1; j < 10; j++)
{
everybox[i - 1, j - 1] = new TextBox
{
Location = new Point((int)(scale * i), (int)(scale * j)),
Size = new Size((int)scale - 2, (int)scale - 2),
Multiline = true
};
Controls.Add(everybox[i - 1, j - 1]);
}
}
ResumeLayout();
}
private void Form1_Paint(object sender, PaintEventArgs e)
{
float scale = ClientSize.Width / 11;
Graphics g = this.CreateGraphics();
int counter = 0;
for (float i = scale; i <= this.ClientSize.Width - scale; i += scale)
{
counter++;
if ((counter - 1) % 3 != 0)
{
g.DrawLine(new Pen(Color.Black), new Point((int)i, (int)scale),
new Point((int)i, ClientSize.Width - (int)scale));
g.DrawLine(new Pen(Color.Black), new Point((int)scale, (int)i),
new Point(ClientSize.Width - (int)scale, (int)i));
}
else
{
g.DrawLine(new Pen(Color.Black, 3f), new Point((int)i, (int)scale),
new Point((int)i, ClientSize.Width - (int)scale));
g.DrawLine(new Pen(Color.Black, 3f), new Point((int)scale, (int)i),
new Point(ClientSize.Width - (int)scale, (int)i));
}
}
}
It is rather annoying, and causes noticeable lag. everybox is a TextBox[9,9] object.
Per my comment, change:
Graphics g = this.CreateGraphics();
to
e.Graphics
Paint can definitely get called alot and if you are getting too many calls, it probably has nothing to do with this bit of code. One thing that would help the performance of this particular bit is to try reducing the amount of work you do...
Graphics g = e.Graphics;
Pen bp = new Pen(Color.Black, 3f);
Point start = new Point(0,0);
Point stop = new Point(0,0);
for (float i = scale; i <= this.ClientSize.Width - scale; i += scale)
{
int iAsInt = (int)i;
int scaleAsInt = (int)scale;
int w = ClientSize.Width;
counter++;
if ((counter - 1) % 3 != 0)
{
start.X = iAsInt;
start.Y = scaleAsInt;
stop.X = iAsInt;
stop.Y = w-scaleAsInt;
g.DrawLine(Pens.Black, start, stop);
start.X = scaleAsInt;
start.Y = iAsInt;
stop.X = w-scaleAsInt;
stop.Y = iAsInt;
g.DrawLine(Pens.Black, start, stop);
// Note: this looks like more work, but it is actually less
// your code still has to make all the assignments in addition to
// newing up the points (and later having to garbage collect them)
}
else
{
// TODO: reuse the start/stop points here
g.DrawLine(bp, new Point(iAsInt, scaleAsInt), new Point(iAsInt, w - scaleAsInt);
g.DrawLine(bp, new Point(scaleAsInt, iAsInt), new Point(w - scaleAsInt, iAsInt));
}
}
To specifically stop the overdrawing of your lines, look at the ClipRectangle member of PaintEventArgs. If part of your line falls within the area of the clip rectangle, redraw it.
A possible reason is because you are trying to draw too many heavy-weight components. If my math is correct you are redrawing 9 * 9 * 9 * 9 = 6561 objects. WinForms are not designed to support redrawing of that many components in the efficient way.
You may need to think if you really need to use that many heavy-weight graphic components with WinForms. There might be lighter components or you can switch to XNA (which has camera, views etc - all of that reduce the number of objects needed to be redrawn) or WPF depending on the context.
The code you posted causes the paint event to fire 81 times (9*9). Once for each control being added to the form. Any more times are due to something that invalidates the form, like the mouse moving over it, another window moving over it, or the form resizing. Some code you aren't showing us may be responsible.
Here's the problem:
I've been working on a little game where monsters bounce off the walls (edges) of the main form, and it's going swimmingly, but it only paints one of each type of monster when it should be iterating through a list of each of them and calling their OnPaint and Move methods:
private void Pacmen_Paint(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
Rectangle rect = e.ClipRectangle;
g.Clear(backgroundColor);
foreach (Hydra h in hydraList) {
h.OnPaint(e);
h.Move(e);
} // end foreach
foreach (Ghost gh in ghostList) {
gh.OnPaint(e);
gh.Move(e);
} // end foreach
}
Here's the ghost's methods:
public void OnPaint(PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.HighQuality;
GraphicsPath path = new GraphicsPath();
SolidBrush fillBrush = new SolidBrush(color);
SolidBrush eyeBrush = new SolidBrush(Color.Black);
path.AddArc(pos, (float)180, (float)180);
path.AddLine((float)pos.Right, (float)(pos.Y + pos.Height / 2),
(float)pos.Right, (float)pos.Bottom);
path.AddLine((float)pos.Right, (float)pos.Bottom,
(float)(pos.X + pos.Width / 2), (float)(pos.Bottom - radius / 2));
path.AddLine((float)(pos.X + pos.Width / 2), (float)(pos.Bottom - radius / 2),
(float)pos.Left, (float)pos.Bottom);
path.AddLine((float)pos.Left, (float)pos.Bottom,
(float)pos.Left, (float)(pos.Y + pos.Height / 2));
g.FillPath(fillBrush, path);
g.FillEllipse(eyeBrush, new Rectangle(pos.X + pos.Width / 4, pos.Y + pos.Height / 4, radius / 4, radius / 5));
g.FillEllipse(eyeBrush, new Rectangle(pos.X + 3 * pos.Width / 4, pos.Y + pos.Height / 4, radius / 4, radius / 5));
} // end OnPaint
public void Move(PaintEventArgs e)
{
pos.Offset(xSpeed, ySpeed);
}
Any ideas why only one would show up? Thanks!
Are you sure you're giving the characters individual starting positions and speed? Maybe they are all painting, but on exactly the same spot?
You are calling the OnPaint for each hydra and ghost by using the same PaintEventArgs passed to Pacmen_Paint. Maybe the OnPaint methods are not using the correct Graphics object then.
Try replacing the body of your Ghost method with simple:
Console.WriteLine("Ghost at " + pos.X + ", " + pos.Y);
Then run the app and check the Output window in VS to see where they are drawn exactly.
Other notes (others may have commented already):
Use the using construct to dispose Brushes and other disposable graphics objects inside the Paint method, or cache them and make your Ghost and Hydra objects implement IDisposable to dispose them when they are not needed anymore.
You may get some speed improvements if you simply create a Bitmap field containg an already drawn ghost, and then simply draw it inside Paint. That way you only need to create your graphic objects once (inside using construct, again).