Question was answered. For more information, check out EDIT #4 at the end of this text.
We are currently working on a gamemaking engine which is going pretty well. I am working on the Animation Creator and was wondering if it was possible to draw an image with additive blending.
Let me explain.
We use System.Drawing library of C# and we work with Windows Forms. For now, the user is able to create his animation by importing a framed animation image (an image containing every frame of the animation) and the user is able to drag and drop these frames wherever he wants.
The actual problem is that we can't figure out how to draw a frame with the additive blending.
Here's an exemple of what Additive Blending is if you don't quite get it. I won't blame you, I have a hard time writing in english.
We are using the following method to draw on a Panel or directly on the form. For exemple here's the code to draw a tiled map for the map editor. Since the AnimationManager code is a mess, it'll be clearer with this exemple.
using (Graphics g = Graphics.FromImage(MapBuffer as Image))
using (Brush brush = new SolidBrush(Color.White))
using (Pen pen = new Pen(Color.FromArgb(255, 0, 0, 0), 1))
{
g.FillRectangle(brush, new Rectangle(new Point(0, 0), new Size(CurrentMap.MapSize.Width * TileSize, CurrentMap.MapSize.Height * TileSize)));
Tile tile = CurrentMap.Tiles[l, x, y];
if (tile.Background != null) g.DrawImage(tile.Background, new Point(tile.X * TileSize, tile.Y * TileSize));
g.DrawRectangle(pen, x * TileSize, y * TileSize, TileSize, TileSize);
}
Is there a possible way of drawing an image with an additive drawing and if so, I'd be forever grateful if someone could point me out how. Thank you.
EDIT #1 :
For drawing images, we are using a color matrix to set hue and alph (opacity) like this:
ColorMatrix matrix = new ColorMatrix
(
new Single[][]
{
new Single[] {r, 0, 0, 0, 0},
new Single[] {0, g, 0, 0, 0},
new Single[] {0, 0, b, 0, 0},
new Single[] {0, 0, 0, a, 0},
new Single[] {0, 0, 0, 0, 1}
}
);
Maybe the color matrix can be used for additive blending?
EDIT #2 :
Just found this article by Mahesh Chand.
After further browsing, it may not be possible with a color matrix even though it can accomplish a lot regarding color transformations.
I will answer my own question if solution found.
Thank you for you help.
EDIT #3 :
XNA has a lot of documentation here about blending. I found the formula used to accomplish additive blending on each pixels of an image.
PixelColor = (source * [1, 1, 1, 1]) + (destination * [1, 1, 1, 1])
Maybe there's a way of using this formula in the current context?
I will start a 50 bounty on next edit, we really need this to work.
Thank you again for your time.
EDIT #4
Thanks to axon, now the problem is solved. Using XNA and its Spritebatch, you can accomplish Additive blending doing so :
First of all you create a GraphicsDevice and a SpriteBatch
// In the following example, we want to draw inside a Panel called PN_Canvas.
// If you want to draw directly on the form, simply use "this" if you
// write the following code in your form class
PresentationParameters pp = new PresentationParameters();
// Replace PN_Canvas with the control to be drawn on
pp.BackBufferHeight = PN_Canvas.Height;
pp.BackBufferWidth = PN_Canvas.Width;
pp.DeviceWindowHandle = PN_Canvas.Handle;
pp.IsFullScreen = false;
device = new GraphicsDevice(GraphicsAdapter.DefaultAdapter, GraphicsProfile.Reach, pp);
batch = new SpriteBatch(device);
Then, when it's time to draw on the control or on the form (with the OnPaint event for example), you can use the following code block
// You should always clear the GraphicsDevice first
device.Clear(Microsoft.Xna.Framework.Color.Black);
// Note the last parameter of Begin method
batch.Begin(SpriteSortMode.BackToFront, BlendState.Additive);
batch.draw( /* Things you want to draw, positions and other infos */ );
batch.End();
// The Present method will draw buffer onto control or form
device.Present();
Either use 1) XNA (recommended for speed), or 2) use pixel-operations in C#. There may be other methods, but either of these work (I'm using each of them for 3D effects and image analysis apps (respectively) that I maintain).
Pixel Operations in C#:
Using 3 bitmaps; bmpA, bmpB, bmpC, where you want to store bmpA+bmpB in bmpC.
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++)
{
Color cA = bmpA.GetPixel(x,y);
Color cB = bmpB.GetPixel(x,y);
Color cC = Color.FromArgb(cA.A, cA.R + cB.R, cA.G + cB.G, cA.B + cB.B);
bmpC.SetPixel(x, y, cC);
}
}
The above code is very slow. A faster solution in C# could use pointers like this:
// Assumes all bitmaps are the same size and same pixel format
BitmapData bmpDataA = bmpA.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
BitmapData bmpDataB = bmpB.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.ReadOnly, bmpA.PixelFormat);
BitmapData bmpDataC = bmpC.LockBits(new Rectangle(0, 0, bmpA.Width, bmpA.Height), ImageLockMode.WriteOnly, bmpA.PixelFormat);
void* pBmpA = bmpDataA.Scan0.ToPointer();
void* pBmpB = bmpDataB.Scan0.ToPointer();
void* pBmpC = bmpDataC.Scan0.ToPointer();
int bytesPerPix = bmpDataA.Stride / bmpA.Width;
for (int y = 0; y < bmp.Height; y++)
{
for (int x = 0; x < bmp.Width; x++, pBmpA += bytesPerPix, pBmpB += bytesPerPix, pBmpC += bytesPerPix)
{
*(byte*)(pBmpC) = *(byte*)(pBmpA) + *(byte*)(pBmpB); // R
*(byte*)(pBmpC + 1) = *(byte*)(pBmpA + 1) + *(byte*)(pBmpB + 1); // G
*(byte*)(pBmpC + 2) = *(byte*)(pBmpA + 2) + *(byte*)(pBmpB + 2); // B
}
}
bmpA.UnlockBits(bmpDataA);
bmpB.UnlockBits(bmpDataB);
bmpC.UnlockBits(bmpDataC);
The above method requires pointers and hence must be compiled with the "unsafe" directive. Also assumes 1-byte for each of R,G, and B. Change the code to suit your pixel format.
Using XNA is a lot faster (performance) since it is hardware accelerated (by the GPU). It basically consists of the following:
1. Create the geometry needed to draw the image (a rectangle, most likely a full-screen quad).
2. Write a vertex-shader and pixel-shader. The vertex-shader can simply pass-through the geometry unmodified. Or you can apply an orthogonal projection (depending on what coordinates you want to work with for the quad). The pixel shader will have the following lines (HLSL):
float4 ps(vertexOutput IN) : COLOR
{
float3 a = tex2D(ColorSampler,IN.UV).rgb;
float3 b = tex2D(ColorSampler2,IN.UV).rgb;
return float4(a + b,1.0f);
}
There are different methods available for accessing textures. The following will also work (depending on how you want the XNA code to bind to the shader parameters):
float4 ps(vertexOutput IN) : COLOR
{
float3 a = texA.Sample(samplerState, IN.UV).xyz;
float3 b = texB.Sample(samplerState, IN.UV).xyz;
return float4(a + b,1.0f);
}
Which of the above shaders you use will depend on whether you want to use the "sampler2D" or "texture" HLSL interfaces to access the textures.
You should also be careful to use an appropriate sampler setting to ensure that no sampling (e.g. linear interpolation) is used when looking up colour values unless that's something you want (in which case use something higher-quality/higher-order).
XNA also has built-in BlendStates you can use to specify how overlapped textures will be combined. I.e. BlendState.Additive (see updated original post).
Related
I have been trying to accomplish this for quite some time with no success; I've looked at several relevant questions here on StackOverflow with no success; I've also followed 6 different tutorials that ALL followed pretty much the same process:
Build the vertices: -1, -1, 0, -1, 1, 0, 1, 1, 0, and 1, -1, 0.
Build the indices: 0, 1, 2, 0, 2, 3.
Create the Vertex and Index buffers.
Clear the RenderTargetView.
Set the current Vertex and Pixel shaders.
Update the constant buffers if you have any.
Render the quad (see below).
Rinse and repeat 4 - 8.
Now, the reason this is driving me up the wall is because I can render far more advanced objects such as:
Spheres
3D Lines
Fans
My code for creating the quad is pretty much the same as everyone else's:
public class Quad {
private void SetVertices() {
float width = Rescale(Size.Width, 0, Application.Width, -1, 1);
float height = Rescale(Size.Height, 0, Application.Height, -1, 1);
vertices = new Vertex[] {
new Vertex(new Vector3(-width, -height, 0), Vector3.ForwardLH, Color.White),
new Vertex(new Vector3(-width, height, 0), Vector3.ForwardLH, Color.White),
new Vertex(new Vector3(width, height, 0), Vector3.ForwardLH, Color.White),
new Vertex(new Vector3(width, -height, 0), Vector3.ForwardLH, Color.White)
}
indices = new int[] { 0, 1, 2, 0, 2, 3 };
vertexBuffer = Buffer.Create(Device3D, BindFlags.VertexBuffer, vertices);
vertexBinding = new VertexBufferBinding(vertexBuffer, Utilities.SIzeOf<Vertex>(), 0);
indexBuffer = Buffer.Create(Device3D, BindFlags.IndexBuffer, indices);
indexCount = indices.Length;
}
public void Render() {
if (shaderResourceView != null)
context3D.PixelShader.SetShaderResource(0, shaderResourceView);
context3D.PixelShader.SetSampler(0, samplerState);
context3D.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleStrip;
context3D.InputAssembler.SetVertexBuffers(0, vertexBinding);
context3D.InputAssembler.SetIndexBuffer(indexBuffer, Format.R32_UInt, 0);
context3D.DrawIndexed(totalIndexCount, 0, 0);
}
}
Notes
I am using a right handed coordinate system (for some reason the previous developer hardwired Vector3.ForwardLH into some places that I now cannot get rid of yet); if that helps any and I cannot currently convert to a left handed coordinate system.
Am I missing something here? Why am I unable to render a basic quad?
If you feel more information is needed feel free to let me know and I will add it on request.
When rendering with Direct3D 11, you need to know the all the state. You do not mention what your BlendState, DepthStencilState, or RasterState settings are here which is a likely reason you aren't getting the results you want.
If the DepthStencilState is such set use the Z-Buffer, then the fact that your vertices have a 0 for the Z means they are going to get culled. You can set a depth/stencil state without Z writes or Z tests, and you should definitely turn off Z writes when drawing 2D stuff anyhow. You can also pass something like 0.5 for the Z value for your vertices which is fairly common for 2D drawing with 3D APIs.
If you backface culling enabled in the RasterState, then the winding order of your vertices could result in them being skipped. You can play with different winding orders, or disable culling.
It also really matters what your Vertex Shader and Pixel Shader are here. You don't show the code for setting your shaders or shader constants, and you don't show the HLSL.
You should seriously consider using the SharpDX Toolkit SpriteBatch class for efficient quad rendering or looking at their source for it.
I know you are using SharpDX an C#, but you might find it useful to see the DirectX Tool Kit for DX11 tutorial on drawing 2D shapes.
I need to remove the black background color of a bitmap in C# VS2013.
It is just like that I draw some points on a canvas. The canvas is black and I just need to change the canvas to be transparent meanwhile keeping colorful points on it without any changes.
I got the solution at:
How to remove white background color from Bitmap
Bitmap capcha = new Bitmap(#"C:/image.png");
capcha.MakeTransparent(Color.Black);
But, the background still have a gray color like a fog covering the points on the image.
How to remove it ?
UPDATE
I used the code:
ImageAttribute imageAttribute = new ImageAttribute();
imageAttribute.SetGamma(0.5f, ColorAdjustType.Bitmap);
gr.DrawImage(img, new Rectangle(0, 0, img.Width, img.Height),
0, 0, img.Width, img.Height, GraphicsUnit.Pixel, imageAttribute );
I got same thing.
More update of C# code to draw an image :
System.Drawing.Bitmap canvasImage = new System.Drawing.Bitmap(xSize, ySize, System.Drawing.Imaging.PixelFormat.Format32bppArgb);
canvasImage.MakeTransparent(Color.Black);
Graphics g = Graphics.FromImage(canvasImage);
System.Drawing.Bitmap tempImage = myDrawImageFunction(myPoints);
g.Clear(Color.Transparent); // this only give me an empty image without any points on it. But, if I uncomment it, I will have an image with black background.
// my understanding about g.DrawImage() is to draw points on tempImage
// after clearing the background. But, the image still have a foggy covering on the image.
g.DrawImage(tempImage, new System.Drawing.PointF(x_position, y_position));
I want to have a transparent background for "tempImage" before any points are drawn on it.
The example image has a back ground that needs to be removed but the colorful points on the image need to be kept without any changes.
This will do the job:
public Color MakeTransparent(Color c, int threshold)
{ // calculate the weighed brightness:
byte val = (byte)((c.R * 0.299f + c.G * 0.587f + c.B * 0.114f));
return val < threshold ? Color.FromArgb(0, c.R, c.G, c.B) : c;
}
You could use it in a double loop over the pixels, but for fast results you should call it from the code in this post (second part!) which uses LockBits.
Change this
ModifyHue hueChanger = new ModifyHue(MaxChannel);
to the new function:
ModifyHue hueChanger = new ModifyHue(MakeTransparent);
and call it with a suitable threshold, maybe 10 or 20..:
c = hueChanger(c, 20);
The function skips the call to the system's MakeTransparent function and directly sets the alpha channel of each pixel to 0.
If you want to create a uniform color instead of a transparent one it should be easy to modify (e.g. by returning Color.FromArgb(255, 0, 0, 0) for solid black)
Do note that, while the code in the linked post takes both 24 and 32 bbp formats you should definitely not save as JPG, as this will re-introduce artifacts and the result will not work well with e.g. a TransparencyKey color..
Instead do save it as PNG, as Hans suggests!
I hope you can modify the button code to a function :-)
I'm attempting to load an image from the filesystem, re-color it, then save it to a Stream. The images in question are fairly simple, single-color, geometric shapes.
I have it working, but the resulting images are heavily pixelated along the edges.
I've tried System.Drawing:
var colorMap = new ColorMap
{
OldColor = Color.FromArgb(255, 255, 255, 255),
NewColor = Color.FromArgb(255, 255, 0, 0)
};
var imageAttrs = new ImageAttributes();
imageAttrs.SetRemapTable(new[] {colorMap});
var newImage = new Bitmap(image.Width, image.Height);
var graphics = Graphics.FromImage(newImage);
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.DrawImage(image,
new Rectangle(0, 0, image.Width, image.Height),
0, 0,
image.Width,
image.Height,
GraphicsUnit.Pixel,
imageAttrs);
I've also tried the ImageProcessor library, using its ReplaceColor() method, but I get the same results (although not quite as bad).
Any way to do this and preserve the nice smooth edges my original image had?
The images in question are fairly simple, single-color, geometric
shapes.
Sounds fine but the problem is that your 'Table' of colors is far too short unless the images really and truely contain only the one color you put into the map! But those images most certainly have been drawn with anti-aliasing on and therefore all anti-aliased pixels are not covered by the Table. You need to
Either use images without antialiasing, but they won't be as smooth as you want it
Or build a proper ColorMap, see below
Or write a function of your own, best using Lockbits for speed..
Or you can try to achieve the color changes with a ColorMatrix which combines speed and 'color smartness'. However not all changes lend itself to using it.. So you may want to tell us about the kind of changes you will need..
You are not alone:
I just tried the example on msdn because it looks so wrong: Saving Jpg files should not work (as it will always generate its own color tables) and look and behold it works, but only because the files created on my machine are Png files with the wrong extension! Once you add ImageFormat.Jpeg to the save it stops working..:
Original MSDN code:
myBitmap.Save("Circle2.jpg");
Changed to
myBitmap.Save("Circle2.jpg", ImageFormat.Jpeg);
Results:
To build a proper ColorMap you will need to a) loop over all pixels and collect all distinct colors (simple but slow) and then calculate the target colors (fast but not necessarily simple or well-defined.)
Here are two routines that show how to build a complete ColorMap:
List<Color> GetDistinctColors(Bitmap bmp)
{
List<Color> colors = new List<Color>();
for (int y = 0; y < bmp.Height; y++)
for (int x = 0; x < bmp.Width; x++)
{
Color c = bmp.GetPixel(x,y);
if (!colors.Contains(c)) colors.Add(c);
}
return colors;
}
List<Color> ChangeColors(List<Color> colors)
{
List<Color> newColors = new List<Color>();
foreach(Color c in colors)
{
int A = 255; // here you need..
int R = c.G; // ..to write..
int G = c.R; // ..your custom .
int B = c.B; // ..color change code!!
newColors.Add(Color.FromArgb(A,R,G,B));
}
return newColors;
}
To use it you write:
// prepare the two lists:
List<Color> colors = GetDistinctColors((Bitmap)myImage);
List<Color> newColors = ChangeColors(colors);
// Create a complete color map
ColorMap[] myColorMap = new ColorMap[colors.Count];
for (int i = 0; i < colors.Count; i++)
{
myColorMap[i] = new ColorMap();
myColorMap[i].OldColor = colors[i];
myColorMap[i].NewColor = newColors[i];
}
Note that writing the correct code for the ChangeColor function is anything but simple. Not only will you have to know what you want, you also need the right tools to achieve it. In my code example above I have done a very simplistic channel swap. This will usually not result in what you want: Neither the prime colors nor the anti-aliased pixels can be changed so simply. Instead you should transform from RGB to HSL or HSV and change the hue there! See here for a SetHue example!
I am trying to draw a crosshair ("plus sign") with inverted colors over an image to show the location of a selected point within the image. This is how I do it:
private static void DrawInvertedCrosshair(Graphics g, Image img, PointF location, float length, float width)
{
float halfLength = length / 2f;
float halfWidth = width / 2f;
Rectangle absHorizRect = Rectangle.Round(new RectangleF(location.X - halfLength, location.Y - halfWidth, length, width));
Rectangle absVertRect = Rectangle.Round(new RectangleF(location.X - halfWidth, location.Y - halfLength, width, length));
ImageAttributes attributes = new ImageAttributes();
float[][] invertMatrix =
{
new float[] {-1, 0, 0, 0, 0 },
new float[] { 0, -1, 0, 0, 0 },
new float[] { 0, 0, -1, 0, 0 },
new float[] { 0, 0, 0, 1, 0 },
new float[] { 1, 1, 1, 0, 1 }
};
ColorMatrix matrix = new ColorMatrix(invertMatrix);
attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);
g.DrawImage(img, absHorizRect, absHorizRect.X, absHorizRect.Y, absHorizRect.Width, absHorizRect.Height, GraphicsUnit.Pixel, attributes);
g.DrawImage(img, absVertRect, absVertRect.X, absVertRect.Y, absVertRect.Width, absVertRect.Height, GraphicsUnit.Pixel, attributes);
}
It works as expected, however, it is really slow. I want the user to be able to move the selected location around with their mouse by setting the location to the cursor's location whenever it moves. Unfortunately, on my computer, it can update only around once per second for big images.
So, I am looking for an alternative to using Graphics.DrawImage to invert a region of an image. Are there any ways to do this with speeds proportional to the selected region area rather than the entire image area?
Sounds to me you are focusing on the wrong problem. Painting the image is slow, not painting the "cross-hairs".
Large images can certainly be very expensive when you don't help. And System.Drawing makes it very easy to not help. Two basic things you want to do to make the image paint faster, getting it more than 20 times faster is quite achievable:
avoid forcing the image painting code to rescale the image. Instead do it just once so the image can be drawn directly one-to-one without any rescaling. Best time to do so is when you load the image. Possibly again in the control's Resize event handler.
pay attention to the pixel format of the image. The fastest one by a long shot is the pixel format that's directly compatible with the way the image needs to be stored in the video adapter. So the image data can be directly copied to video RAM without having to adjust each individual pixel. That format is PixelFormat.Format32bppPArgb on 99% of all modern machines. Makes a huge difference, it is ten times faster than all the other ones.
A simple helper method that accomplishes both without otherwise dealing with the aspect ratio:
private static Bitmap Resample(Image img, Size size) {
var bmp = new Bitmap(size.Width, size.Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
using (var gr = Graphics.FromImage(bmp)) {
gr.DrawImage(img, new Rectangle(Point.Empty, size));
}
return bmp;
}
Draw the image once on Graphics g, then draw the crosshair on Graphics g directly instead of the image. You can optionally keep track of the places the user clicked so as to save them either in the image or elsewhere as needed.
i have no previous experience in plotting in winforms, in one form i want to plot ecg. or lets say a sin wave or any wave function in a specific area, but what i am doing is e.c.g.. rest of the form will be normal form with buttons and labels,
can anybody be nice enough to through in a tutorial
:)
You have few choices, you can write your own control, that will process data and render it. For more complicated plots, that can be a bit complicated, but the basics are always the same, setting X and Y values ranges and then just draw a line using GDI going from left to right, nothing fancy.
As this can get a bit complicated for more advanced features, you could use some charting controls, I'd read this post or check codeproject.com, I remember, that I saw few attempts to write some decent charting controls, which are open source, new articles will probably be coded in WPF, but you should find something older as well.
Edit:
Some links that you can find useful: Graph plotting lib that's main goal is to simulate ECG or another graph plotting lib
You need to create a custom control.
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
protect override OnPaint(PaintEventArgs pe ){}
Then in the paint function, you draw your graphics the way you want it, let's say sin(x)
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
{
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
}
prevX = x;
prevY = Math.sin(x);
}
To force the ECG to redraw, you call the .Invalidate() function on the control. You should be able to drag and drop the control in your form from the designer.
In the end, the class would look like
public class MyECGDrawer : Control{}
In it, you override the OnPaint event
public class MyECGDrawer : Control
{
protect override OnPaint(PaintEventArgs pe )
{
// refresh background
pe.Graphics.FillRectangle( Brushes.White, 0, 0, Width, Height );
int prevX = -1, prevY = -1;
for(int x = 0; x < Width; x++ )
{
if( prevX >= 0 )
pe.Graphics.DrawLine( Pens.Black, prevX, prevY, x, Math.sin(x) );
prevX = x;
prevY = Math.sin(x);
}
}
}
I wrote up the following and tested it. It seems to do what you want, but note that it is simply plotting sin(x) in a loop with no delay - i.e. the plot for sin(x) streams off the left edge so fast you can hardly see it. You can, however, put a break on any line inside the loop and then step through the loop with F5 to see it work slowly - presumably your streaming ECG data will only arrive at some fixed speed so this should not be a problem in your implementation.
In the following, monitor is a PictureBox on a winforms form. Everything else is local.
private void drawStream(){
const int scaleX = 40;
const int scaleY = 40;
Point monitorTopLeft = new Point(0, 0);
Point MonitorTopLeftMinus1 = new Point(-1, 0);
int halfX = monitor.Width / 2;
int halfY = monitor.Height / 2;
Size size = new Size(halfX + 20, monitor.Height);
Graphics g = monitor.CreateGraphics();
g.TranslateTransform(halfX, halfY);
g.ScaleTransform(scaleX, scaleY);
g.Clear(Color.Black);
g.ResetClip();
float lastY = (float)Math.Sin(0);
float y = lastY;
Pen p = new Pen(Color.White, 0.01F);
float stepX = 1F / scaleX;
for (float x = 0; x < 10; x += stepX) {
g.CopyFromScreen(monitor.PointToScreen(monitorTopLeft), MonitorTopLeftMinus1, size, CopyPixelOperation.SourceCopy);
y = (float)Math.Sin(x);
g.DrawLine(p, -stepX, lastY, 0, y);
lastY = y;
}
}
Some additional info that may be helpful:
The origin in a picture box starts
out at the top left corner.
TranslateTransform allows you to
translate (i.e. move) the origin.
In the example, I translate it by
half the picture box's width and
half its height.
ScaleTransform changes the magnification of the picturebox - note that it even magnifies the width of the pen used to draw on the picturebox - this is why the pen's width is set to 0.01.
CopyFromScreen performs a bitblt. Its source point is relative to the screen, the destination is relative to the picturebox and the size of the rectangle to move disregards any transforms (like the scale and translation transforms we added).
Notice that the X coordinates in the DrawLine method are -stepx and 0. All drawing basically occurs right on the y axis (i.e. x = 0) and then CopyFromScreen moves the drawn portion to the left so that it "streams" off to the left.
Unless you are doing this as a learning experience, you may want to consider looking at the free Microsoft Chart Controls for .NET available here.
http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c&displaylang=en#QuickInfoContainer
That being said, I would offer the following guidelines if you want to roll your own.
Create a user control to encapsulate the plot rendering rather than render directly on the form.
In your control, expose properties to get/set the data you wish to render and add any other properties you want to control the rendering (scaling, panning, colors, etc.)
In you control, either override the OnPaint method or create an event handler for the Paint event. These methods will have a PaintEventArgs object passed to them, which contains a Graphics object as a property. The methods of the Graphics object are used to render points, lines, etc onto the control when it needs to be painted. Most of the drawing operations require either a pen (outlines / lines) or a brush (filled areas). You can use stock objects for these operations (e.g. Pens.Black or Brushes.Red) or you can create your own (see documentation). If you create you own objects, make sure you dispose of them after using them (e.g. using the "using" statement or by calling Dispose).
There are a couple good books on GDI+. I suggest picking one up if you are going in deep.