the following code is a handler that takes in a Percent (percent of the graph to show as blue), Max (maximum value), and a Gallons value (number) to create a thermometer-style progress meter. It outputs a graph which is added to a web form as <img src="Thermometer.ashx" alt="" />.
Since this graph is intended to show progress as the number of gallons of water saved, it would be nice to have the blue color fill up a water barrel image. To do so I have attempted to add a barrel image with a transparent interior to the web form and tried to position it in front of the thermometer image but this has not worked, as it seems impossible to layer an image on top of one created using a handler.
My question is this: is it possible to added the barrel image through code so that is serves as the outline for the blue thermometer? Or does the barrel image need to be created from scratch in the code? I fear the latter since I am probably unable to code it.
Please see below and let me know if there is anything I can do.
Thank you!
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Drawing;
using System.Net;
using System.Drawing.Imaging;
using System.Web.UI.HtmlControls;
namespace rainbarrel
{
public class Thermometer : IHttpHandler
{
public bool IsReusable
{
get { return false; }
}
public void ProcessRequest(HttpContext context)
{
int Percent = 25;
int Max = 20000;
bool Gallon = true;
float Pixels = (float)Percent * 2.15f;
Bitmap TempImage = new Bitmap(200, 250);
Graphics TempGraphics = Graphics.FromImage(TempImage);
TempGraphics.FillRectangle(Brushes.White, new Rectangle(0, 0, 200, 250));
Pen TempPen = new Pen(Color.Black);
BarrelFill(TempImage, Percent);
bool DrawText = true;
float Amount = Max;
for (float y = 20.0f; y < 235.0f; y += 10.75f)
{
TempGraphics.DrawLine(TempPen, 119, y, 125, y);
if (DrawText)
{
Font TempFont = new Font("Arial", 8.0f, FontStyle.Regular);
if (Gallon)
{
TempGraphics.DrawString(Amount.ToString() + " Gal", TempFont, Brushes.Black, 130, y);
}
else
{
TempGraphics.DrawString(Amount.ToString(), TempFont, Brushes.Black, 130, y);
}
DrawText = false;
}
else DrawText = true;
Amount -= ((Max / 100) * 5.0f);
}
string etag = "\"" + Percent.GetHashCode() + "\"";
string incomingEtag = context.Request.Headers["If-None-Match"];
context.Response.Cache.SetExpires(DateTime.Now.ToUniversalTime().AddDays(1));
context.Response.Cache.SetCacheability(HttpCacheability.Public);
context.Response.Cache.SetMaxAge(new TimeSpan(7, 0, 0, 0));
context.Response.Cache.SetRevalidation(HttpCacheRevalidation.AllCaches);
context.Response.Cache.SetETag(etag);
if (String.Compare(incomingEtag, etag) == 0)
{
context.Response.StatusCode = (int)HttpStatusCode.NotModified;
context.Response.End();
}
else
{
context.Response.ContentType = "image/Gif";
TempImage.Save(context.Response.OutputStream, ImageFormat.Gif);
TempImage.MakeTransparent();
}
}
private void BarrelFill(Bitmap TempImage, int Percent)
{
if (Percent == 100)
{
FillRectangle(TempImage, 60, 20, 235);
}
else
{
FillRectangle(TempImage, 60, (int)(235.0f - ((float)Percent * 2.15f)), 235);
}
}
private void FillRectangle(Bitmap TempImage, int x, int y1, int y2)
{
int MaxDistance = 50;
for (int i = x - MaxDistance; i < x + MaxDistance; ++i)
{
for (int j = y1; j < y2; ++j)
{
float Distance = (float)Math.Abs((i - x));
int BlueColor = (int)(255.0f * (1.0 - (Distance / (2.0f * MaxDistance))));
TempImage.SetPixel(i, j, Color.FromArgb(30, 144, BlueColor));
}
}
}
}
}
UPDATE:
I found this post here which showed me how to solve:
overlaying images with GDI+
Here is my solution:
Image Barrel = Image.FromFile("C:\\Inetpub\\wwwroot\\barrel\\barrel_trans.png");
TempGraphics.DrawImage(Barrel, -5, 0, 120, 240);
Barrel.Dispose();
However, The quality of the png layer is bad (slightly blurry), although the original is very sharp and clear. Is there a way to retain the image quality of the original?
Many thanks in advance for your help.
Changing TempImage.Save(context.Response.OutputStream, ImageFormat.gif); to Jpeg solved the quality issue.
I believe that the best way to accomplish this would be to create the entire image in C# and send that down. You can get exact positioning of the barrel over the 'thermometer', and if the barrel is already a (semi) transparent PNG, you can just do a .DrawImage right over the thermostat, add text, and do anything you want before sending back the final image directly into the ResponseStream.
A couple of other suggestions for you:
Since you are handing everything directly in your Handler, you should dispose of your own internal disposable resources. "Using" blocks are the easiest way. You will need at least these two:
using (Graphics TempGraphics = GetGraphicsFromBitmap(TempImage))
{
for (float y = 20.0f; y < 235.0f; y += 10.75f)
{
TempGraphics.DrawLine(TempPen, 119, y, 125, y);
...
}
}
and
using (Font TempFont = new Font("Arial", 8.0f, FontStyle.Regular))
{
...
}
Related
In a new class top:
private Random r = new Random();
public int numberOfPoints = 100;
public Bitmap bmpWithPoints;
Then in the class Initi method:
public void Init()
{
bmpWithPoints = GetBitmapWithEllipses(1.0f);
}
Then then method GetBitmapWithEllipses:
private Bitmap GetBitmapWithEllipses(float radius)
{
Bitmap bmp = new Bitmap(512, 512);
Random r = new Random();
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Black);
g.SmoothingMode = SmoothingMode.AntiAlias;
List<RectangleF> rects = new List<RectangleF>();
rects.Add(new RectangleF(0, 0, 0, 0));
for (int x = 0; x < numberOfPoints; x++)
{
for (int y = 0; y < numberOfPoints; y++)
{
Color c = Color.FromArgb(
r.Next(0, 256),
r.Next(0, 256),
r.Next(0, 256));
PointF p = new PointF(r.Next(bmp.Width), r.Next(bmp.Height));
RectangleF rect = new RectangleF(p, new SizeF(radius * 2, radius * 2));
if (!rects.Any(tmp => tmp.IntersectsWith(rect)))
{
rects.Add(rect);
g.FillEllipse(new SolidBrush(c), rect);
}
else
{
y--;
}
}
}
}
return bmp;
}
In Form1 top and constructor:
DopplerEffect de = new DopplerEffect();
public Form1()
{
InitializeComponent();
de.bmpWithPoints = new Bitmap(512, 512);
de.numberOfPoints = 50;
de.Init();
}
Before i had a problem the method GetBitmapWithEllipses was slow because when setting the number of points to 50. Now this part is working fast but now when changing the radius size to 5.0f or 10.0f it's very slow.
When i set the number of points to 10 or 20 or 50 in Form1 in the line:
de.numberOfPoints = 50;
The method GetBitmapWithEllipses in the class will work fast.
But if i change in the class the radius size of the points each point for example to 1.0f it will work fast. But if i set the number of points to 50 and change the radius size to 10.0f for example the method GetBitmapWithEllipses will work very slow. Even to 5.0f
Is there any way to make it work fast also when changing the radius size for each point ?
You randomly create circles in the loop and if the new created circle has an overlap with previous created circles you step the loop backward.
When the radios grows the chance of overlap increases, so you step backward more and the loop takes too long to finish. In fact there is no guarantee for your loop to even finish!
You can comment the code which checks for overlap and the code which makes the loop step backward, then you will have a fast result:
//if (!rects.Any(tmp => tmp.IntersectsWith(rect)))
//{
// rects.Add(rect);
g.FillEllipse(new SolidBrush(c), rect);
//}
//else
//{
// y--;
//}
I am fully aware that I can load textures in OpenTK. But when I tried to render a picture using only points it shows really weird lines when ClientSize.Width is EXACTLY equal to the width of the picture being rendered. Like this:
And if I resize (enlarge) the ClientSize.Width of the window lines become kinda normal and there is actual reason they appear (they cover areas that can't be rendered):
This seems to occur regardless of the picture I open. Can somebody explain why there are lines in the first picture?
using System;
using System.Drawing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
namespace Miracle
{
class Program
{
public static void Main()
{
using (Window w = new Window())
w.Run(30);
}
}
class Window : GameWindow
{
private Color[,] pixels;
private int width, height;
private bool blink = true;
private int blinkcounter;
public Window() : base(1337, 666, GraphicsMode.Default, "Miracle", GameWindowFlags.Default) { }
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
Bitmap pic = new Bitmap("sample.png");
width = pic.Width;
height = pic.Height;
ClientSize = new Size(width, height);
pixels = new Color[width, height];
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
pixels[x, y] = pic.GetPixel(x, y);
GL.ClearColor(Color.FromArgb(0, 255, 0));
GL.Ortho(0, width, height, 0, -1, 1);
}
protected override void OnResize(EventArgs e)
{
base.OnResize(e);
Title = ClientSize.Width + "x" + ClientSize.Height + (ClientSize.Width == width && ClientSize.Height == height ? " (original)" : "");
GL.Viewport(ClientSize);
}
protected override void OnUpdateFrame(FrameEventArgs e)
{
base.OnUpdateFrame(e);
if (blinkcounter == 6)
{
GL.ClearColor(blink ? Color.FromArgb(255, 0, 0) : Color.FromArgb(0, 255, 0));
blink = !blink;
blinkcounter = 0;
}
blinkcounter++;
}
protected override void OnRenderFrame(FrameEventArgs e)
{
base.OnRenderFrame(e);
GL.Clear(ClearBufferMask.ColorBufferBit);
GL.MatrixMode(MatrixMode.Projection);
GL.Begin(PrimitiveType.Points);
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
GL.Color4(pixels[x, y]);
GL.Vertex2(x, y);
}
GL.End();
SwapBuffers();
}
}
}
I will describe a solution to your problem and also why I think you're getting the problem.
First, why I think you're getting the problem:
The rasterization of points is weird and hardware dependent. This problem looks to be an error of floating point accuracy in the rasterization stage for points. The projected point is on the boundary of choosing between 2 pixels, and it chooses the wrong one due to FP limitations.
Solution:
Rasterizing an image using points is NOT recommended. That's not what point rasterization is used for. Instead, rasterize a full screen quad, give each vertex of the quad a texture coordinate, and in the fragment shader use the interpolated texture coordinate to fetch from the texture storing the image you want to render. This avoids the problem you were experiencing with the GL_POINTS because it ensures every pixel is drawn to once.
I am asking this question as the other one is two years old and not answered accurately.
I'm looking to replicate the PhotoShop effect mentioned in this article in C#. Adobe call it a Color halftone, I think it looks like some sort of rotated CMYK halftone thingy. Either way I don't know how I would do it.
Current code sample is below.
Any ideas?
P.S.
This isn't homework. I'm looking to upgrade the comic book effect I have in my OSS project ImageProcessor.
Progress Update.
So here's some code to show what I have done so far...
I can convert to and from CMYK to RGB fairly easily and accurately enough for my needs and also print out a patterned series of ellipses based on the the intensity of each colour component at a series of points.
What I am stuck at just now is rotating the graphics object for each colour so that the points are laid at the angles specified in the code. Can anyone give me some pointers as how to go about that?
public Image ProcessImage(ImageFactory factory)
{
Bitmap newImage = null;
Image image = factory.Image;
try
{
int width = image.Width;
int height = image.Height;
// These need to be used.
float cyanAngle = 105f;
float magentaAngle = 75f;
float yellowAngle = 90f;
float keylineAngle = 15f;
newImage = new Bitmap(width, height);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
using (Graphics graphics = Graphics.FromImage(newImage))
{
// Reduce the jagged edges.
graphics.SmoothingMode = SmoothingMode.AntiAlias;
graphics.InterpolationMode = InterpolationMode.HighQualityBicubic;
graphics.PixelOffsetMode = PixelOffsetMode.HighQuality;
graphics.CompositingQuality = CompositingQuality.HighQuality;
graphics.Clear(Color.White);
using (FastBitmap sourceBitmap = new FastBitmap(image))
{
for (int y = 0; y < height; y += 4)
{
for (int x = 0; x < width; x += 4)
{
Color color = sourceBitmap.GetPixel(x, y);
if (color != Color.White)
{
CmykColor cmykColor = color;
float cyanBrushRadius = (cmykColor.C / 100) * 3;
graphics.FillEllipse(Brushes.Cyan, x, y, cyanBrushRadius, cyanBrushRadius);
float magentaBrushRadius = (cmykColor.M / 100) * 3;
graphics.FillEllipse(Brushes.Magenta, x, y, magentaBrushRadius, magentaBrushRadius);
float yellowBrushRadius = (cmykColor.Y / 100) * 3;
graphics.FillEllipse(Brushes.Yellow, x, y, yellowBrushRadius, yellowBrushRadius);
float blackBrushRadius = (cmykColor.K / 100) * 3;
graphics.FillEllipse(Brushes.Black, x, y, blackBrushRadius, blackBrushRadius);
}
}
}
}
}
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
Input Image
Current Output
As you can see since the drawn ellipses are not angled colour output is incorrect.
So here's a working solution. It ain't pretty, it ain't fast (2 seconds on my laptop) but the output is good. It doesn't exactly match Photoshop's output though I think they are performing some additional work.
Slight moiré patterns sometimes appear on different test images but descreening is out of scope for the current question.
The code performs the following steps.
Loop through the pixels of the image at a given interval
For each colour component, CMYK draw an ellipse at a given point which is calculated by rotating the current point by the set angle. The dimensions of this ellipse are determined by the level of each colour component at each point.
Create a new image by looping though the pixel points and adding the CMYK colour component values at each point to determine the correct colour to draw to the image.
Output image
The code
public Image ProcessImage(ImageFactory factory)
{
Bitmap cyan = null;
Bitmap magenta = null;
Bitmap yellow = null;
Bitmap keyline = null;
Bitmap newImage = null;
Image image = factory.Image;
try
{
int width = image.Width;
int height = image.Height;
// Angles taken from Wikipedia page.
float cyanAngle = 15f;
float magentaAngle = 75f;
float yellowAngle = 0f;
float keylineAngle = 45f;
int diameter = 4;
float multiplier = 4 * (float)Math.Sqrt(2);
// Cyan color sampled from Wikipedia page.
Brush cyanBrush = new SolidBrush(Color.FromArgb(0, 153, 239));
Brush magentaBrush = Brushes.Magenta;
Brush yellowBrush = Brushes.Yellow;
Brush keylineBrush;
// Create our images.
cyan = new Bitmap(width, height);
magenta = new Bitmap(width, height);
yellow = new Bitmap(width, height);
keyline = new Bitmap(width, height);
newImage = new Bitmap(width, height);
// Ensure the correct resolution is set.
cyan.SetResolution(image.HorizontalResolution, image.VerticalResolution);
magenta.SetResolution(image.HorizontalResolution, image.VerticalResolution);
yellow.SetResolution(image.HorizontalResolution, image.VerticalResolution);
keyline.SetResolution(image.HorizontalResolution, image.VerticalResolution);
newImage.SetResolution(image.HorizontalResolution, image.VerticalResolution);
// Check bounds against this.
Rectangle rectangle = new Rectangle(0, 0, width, height);
using (Graphics graphicsCyan = Graphics.FromImage(cyan))
using (Graphics graphicsMagenta = Graphics.FromImage(magenta))
using (Graphics graphicsYellow = Graphics.FromImage(yellow))
using (Graphics graphicsKeyline = Graphics.FromImage(keyline))
{
// Ensure cleared out.
graphicsCyan.Clear(Color.Transparent);
graphicsMagenta.Clear(Color.Transparent);
graphicsYellow.Clear(Color.Transparent);
graphicsKeyline.Clear(Color.Transparent);
// This is too slow. The graphics object can't be called within a parallel
// loop so we have to do it old school. :(
using (FastBitmap sourceBitmap = new FastBitmap(image))
{
for (int y = -height * 2; y < height * 2; y += diameter)
{
for (int x = -width * 2; x < width * 2; x += diameter)
{
Color color;
CmykColor cmykColor;
float brushWidth;
// Cyan
Point rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), cyanAngle);
int angledX = rotatedPoint.X;
int angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.C / 255f) * multiplier;
graphicsCyan.FillEllipse(cyanBrush, angledX, angledY, brushWidth, brushWidth);
}
// Magenta
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), magentaAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.M / 255f) * multiplier;
graphicsMagenta.FillEllipse(magentaBrush, angledX, angledY, brushWidth, brushWidth);
}
// Yellow
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), yellowAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.Y / 255f) * multiplier;
graphicsYellow.FillEllipse(yellowBrush, angledX, angledY, brushWidth, brushWidth);
}
// Keyline
rotatedPoint = RotatePoint(new Point(x, y), new Point(0, 0), keylineAngle);
angledX = rotatedPoint.X;
angledY = rotatedPoint.Y;
if (rectangle.Contains(new Point(angledX, angledY)))
{
color = sourceBitmap.GetPixel(angledX, angledY);
cmykColor = color;
brushWidth = diameter * (cmykColor.K / 255f) * multiplier;
// Just using blck is too dark.
keylineBrush = new SolidBrush(CmykColor.FromCmykColor(0, 0, 0, cmykColor.K));
graphicsKeyline.FillEllipse(keylineBrush, angledX, angledY, brushWidth, brushWidth);
}
}
}
}
// Set our white background.
using (Graphics graphics = Graphics.FromImage(newImage))
{
graphics.Clear(Color.White);
}
// Blend the colors now to mimic adaptive blending.
using (FastBitmap cyanBitmap = new FastBitmap(cyan))
using (FastBitmap magentaBitmap = new FastBitmap(magenta))
using (FastBitmap yellowBitmap = new FastBitmap(yellow))
using (FastBitmap keylineBitmap = new FastBitmap(keyline))
using (FastBitmap destinationBitmap = new FastBitmap(newImage))
{
Parallel.For(
0,
height,
y =>
{
for (int x = 0; x < width; x++)
{
// ReSharper disable AccessToDisposedClosure
Color cyanPixel = cyanBitmap.GetPixel(x, y);
Color magentaPixel = magentaBitmap.GetPixel(x, y);
Color yellowPixel = yellowBitmap.GetPixel(x, y);
Color keylinePixel = keylineBitmap.GetPixel(x, y);
CmykColor blended = cyanPixel.AddAsCmykColor(magentaPixel, yellowPixel, keylinePixel);
destinationBitmap.SetPixel(x, y, blended);
// ReSharper restore AccessToDisposedClosure
}
});
}
}
cyan.Dispose();
magenta.Dispose();
yellow.Dispose();
keyline.Dispose();
image.Dispose();
image = newImage;
}
catch (Exception ex)
{
if (cyan != null)
{
cyan.Dispose();
}
if (magenta != null)
{
magenta.Dispose();
}
if (yellow != null)
{
yellow.Dispose();
}
if (keyline != null)
{
keyline.Dispose();
}
if (newImage != null)
{
newImage.Dispose();
}
throw new ImageProcessingException("Error processing image with " + this.GetType().Name, ex);
}
return image;
}
Additional code for rotating the pixels is as follows. This can be found at Rotating a point around another point
I've left out the colour addition code for brevity.
/// <summary>
/// Rotates one point around another
/// <see href="https://stackoverflow.com/questions/13695317/rotate-a-point-around-another-point"/>
/// </summary>
/// <param name="pointToRotate">The point to rotate.</param>
/// <param name="centerPoint">The centre point of rotation.</param>
/// <param name="angleInDegrees">The rotation angle in degrees.</param>
/// <returns>Rotated point</returns>
private static Point RotatePoint(Point pointToRotate, Point centerPoint, double angleInDegrees)
{
double angleInRadians = angleInDegrees * (Math.PI / 180);
double cosTheta = Math.Cos(angleInRadians);
double sinTheta = Math.Sin(angleInRadians);
return new Point
{
X =
(int)
((cosTheta * (pointToRotate.X - centerPoint.X)) -
((sinTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.X)),
Y =
(int)
((sinTheta * (pointToRotate.X - centerPoint.X)) +
((cosTheta * (pointToRotate.Y - centerPoint.Y)) + centerPoint.Y))
};
}
I am now drawing to a panel some dots to indicate a sort of dotted grid with 1% of margin of total panel width.
This is what I am doing now:
private void panel1_Paint(object sender, PaintEventArgs e)
{
Pen my_pen = new Pen(Color.Gray);
int x,y;
int k = 1 ,t = 1;
int onePercentWidth = panel1.Width / 100;
for (y = onePercentWidth; y < panel1.Height-1; y += onePercentWidth)
{
for (x = onePercentWidth; x < panel1.Width-1; x += onePercentWidth)
{
e.Graphics.DrawEllipse(my_pen, x, y, 1, 1);
}
}
}
What is bothering me is that when the app starts I can see the dots being drawn on the panel. Even if it is very quick it still bothers me a lot.
Is it possible to draw the dots on the panel and load it directly drawn?
Thank you for the help
You could create a bitmap and draw it instead.
But before you do that: DrawEllipse is a little expensive. Use DrawLine with a Pen that has a dotted linestyle instead:
int onePercentWidth = panel1.ClientSize.Width / 100;
using (Pen my_pen = new Pen(Color.Gray, 1f))
{
my_pen.DashStyle = System.Drawing.Drawing2D.DashStyle.Custom;
my_pen.DashPattern = new float[] { 1F, onePercentWidth -1 };
for (int y = onePercentWidth; y < panel1.ClientSize.Height - 1; y += onePercentWidth)
e.Graphics.DrawLine(my_pen, 0, y, panel1.ClientSize.Width, y);
}
Note that I am using using so I don't leak the Pen and ClientSize so I use only the inner width. Also note the exaplanation about the custom DashPattern on MSDN
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;