locked
Custom Controls and Graphics RRS feed

  • Question

  • Hi, I've been working on an application for a couple of years now and I'm hitting some brick walls that I hope you guys can help me with. I'm not a programmer (just a physicist with a bit of programming experience) so sometimes I tend to do things the slow way and this is part of my worst problem...
    I needed a graph to display an XYZ plot as X, Y and colour, after looking around I realised that there wasn't anything particularly suitable so I managed to write something that did the job. After running with this for about a year I've come across a number of limitations and am trying to improve the control.
    The problem is that I need to update the data every second and the redraw is far too slow.
    So the data comes in the form of an 800 by 800 array of integers. I parse the array to find the points where the data isn't 0 and only work with those points. The plot still takes more than a second to redraw.

    Here is my code to parse the array and get the points

            private void GetPoints()
            {
                //This function scans the array to find where the data points are so we don't have to look in all the elements when we redraw
                pDataPoints.Clear();
                for (int ixIndex = 0; ixIndex < 800; ixIndex++)
                {
                    for (int iyIndex = 0; iyIndex < 800; iyIndex++)
                    {
                        if (z[ixIndex, iyIndex] > 0)
                        {
                            Point thisPoint = new Point(ixIndex, iyIndex);
                            pDataPoints.Add(thisPoint);
                        }
                        if (z[ixIndex, iyIndex] > maxZ)
                            maxZ = z[ixIndex, iyIndex];
                    }
                }
            }
    Here is my code to plot the points

            private void PlotPoints(PaintEventArgs PaintEvnt)
            {
                Graphics gfx = PaintEvnt.Graphics;
                Pen myPen = new Pen(Color.Black, 1f);
                double Y;
                double X;
                
                //Zoom stuff
                int ximin = (int)((xmin / 0.1) + 400);
                int ximax = (int)((xmax / 0.1) + 400);
                int yimin = (int)((ymin / 0.1) + 400);
                int yimax = (int)((ymax / 0.1) + 400);
                Point myPoint = new Point();
                if (ximin < 0)
                    ximin = 0;
                if (ximax > 800)
                    ximax = 800;
                if (yimin < 0)
                    yimin = 0;
                if (yimax > 800)
                    yimax = 800;
    
                for (int iPoints = 0; iPoints < pDataPoints.Count; iPoints++)
                {
                    X = ((double)pDataPoints[iPoints].X * 0.1) - 40;
                    Y = ((double)pDataPoints[iPoints].Y * 0.1) - 40;
                    myPoint = PositionToPixels(X, Y);
                    myPen.Color = GetColor(z[pDataPoints[iPoints].X, pDataPoints[iPoints].Y]);
                    gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                }
            }
    pDataPoints is a list of type point.

    Any help very much appreciated

    Friday, March 12, 2010 1:52 PM

Answers

  • Hi Haavikon,

    As far as I can see from your code, you do only ever plot one point as one pixel.

    If that's right and I've understood the problem properly, then I think if you modify your points plotting method as below, it should be faster.

    The code uses the lock bits method. All it does is what you do now, but by writing directly to a data array - 'bitmapArray' - instead of using the Graphics methods. This array has the same format that the actual bitmap uses, so once it is created, it is copied to and therefore replaces the bitmap's own internal array. (The code assumes the bitmap is in 32 bits per pixel argb format).

    // Add this somewhere.
    Byte[] bitmapArray = new Byte[1];
    
    private void PlotPoints(PaintEventArgs PaintEvnt)
    {
        //Graphics gfx = PaintEvnt.Graphics;
    
        // Don't need this here.
        // Graphics gfx = Graphics.FromImage(thisPlot);
        Pen myPen = new Pen(Color.Black, 1f);
        double Y;
        double X;
        int ximin = (int)((xmin / 0.1) + 400);
        int ximax = (int)((xmax / 0.1) + 400);
        int yimin = (int)((ymin / 0.1) + 400);
        int yimax = (int)((ymax / 0.1) + 400);
        Point myPoint = new Point();
        if (ximin < 0)
            ximin = 0;
        if (ximax > 800)
            ximax = 800;
        if (yimin < 0)
            yimin = 0;
        if (yimax > 800)
            yimax = 800;
    
    
        // 'Lock' the bitmap's data.
        BitmapData bitmapData = thisPlot.LockBits(new Rectangle(0, 0, thisPlot.Width, thisPlot.Height),
            ImageLockMode.ReadWrite, thisPlot.PixelFormat);
    
        // The stride is the number of bytes per row in the bitmapData.
        int stride = bitmapData.Stride;
    
        Color pixelColor;
        int pixelIndex;
    
        // Check the bitmapArray is big enough.
        int requiredSize = stride * thisPlot.Height;
    
        if (bitmapArray.Length < requiredSize)
        {
            bitmapArray = new Byte[requiredSize];
        }
        
        for (int xindex = ximin; xindex < ximax; xindex++)
        {
            for (int yindex = yimin; yindex < yimax; yindex++)
            {
                if ((z[xindex, yindex] > 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                {
                    X = ((double)xindex * 0.1) - 40;
                    Y = ((double)yindex * 0.1) - 40;
                    myPoint = PositionToPixels(X, Y);
    
                    // Check that the point is within the bounds of the bitmap.
                    if ((myPoint.X >= 0 && myPoint.X < thisPlot.Width) &&
                        (myPoint.Y >= 0 && myPoint.Y < thisPlot.Height))
                    {
                        // Get the index of the point in the bitmapArray.
                        pixelIndex = (myPoint.Y * stride) + (myPoint.X * 4);
    
                        // Get the colour needed.
                        pixelColor = GetColor(z[xindex, yindex]);
    
                        // Now fill the pixel bytes in. The order is Blue, Green, Red, Alpha.
                        bitmapArray[pixelIndex + 0] = pixelColor.B;
                        bitmapArray[pixelIndex + 1] = pixelColor.G;
                        bitmapArray[pixelIndex + 2] = pixelColor.R;
                        bitmapArray[pixelIndex + 3] = pixelColor.A;
                    }
    
                    //myPen.Color = GetColor(z[xindex, yindex]);
                    //gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                }
                if ((z[xindex, yindex] == 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                {
                    X = ((double)xindex * 0.1) - 40;
                    Y = ((double)yindex * 0.1) - 40;
                    myPoint = PositionToPixels(X, Y);
    
                    
                    // Likewise for the zero points.
                    if ((myPoint.X >= 0 && myPoint.X < thisPlot.Width) &&
                        (myPoint.Y >= 0 && myPoint.Y < thisPlot.Height))
                    {
                        // Get the index of the point in the bitmapArray.
                        pixelIndex = (myPoint.Y * stride) + (myPoint.X * 4);
    
                        bitmapArray[pixelIndex + 0] = 0; // -----
                        bitmapArray[pixelIndex + 1] = 0; // Black
                        bitmapArray[pixelIndex + 2] = 0; // -----
                        bitmapArray[pixelIndex + 3] = 255;
                    }
    
                    //myPen.Color = Color.Black;
                    //gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                }
            }
        }
    
        // Done the drawing, so now copy the data back to the bitmap again.
        // This line copies the managed array (bitmapArray) to the
        // unmanaged bitmapData array.
        Marshal.Copy(bitmapArray, 0, bitmapData.Scan0, requiredSize);
    
        // Unlock the bitmap.
        thisPlot.UnlockBits(bitmapData);
    }
    • Proposed as answer by Figo Fei Wednesday, March 17, 2010 1:26 AM
    • Marked as answer by Figo Fei Thursday, March 18, 2010 1:27 AM
    Monday, March 15, 2010 5:00 PM

All replies

  • Plot the points on a bitmap.  When finished, display the bitmap.

    Friday, March 12, 2010 2:53 PM
  • Okay, I've tried your suggestion.
    I create a bitmap object, draw my graph onto it but it's doesn't seem to make any difference. The display is still very slow when you have a large (e.g. 100,000 x,y,z's) amount of data.

    Friday, March 12, 2010 5:08 PM
  • Okay, I've tried your suggestion.
    I create a bitmap object, draw my graph onto it but it's doesn't seem to make any difference. The display is still very slow when you have a large (e.g. 100,000 x,y,z's) amount of data.


    Post your code.  My display (1920 X 1080) isn't slow.  You might want to speed up your drawing code also.  Use Bitmap.LockBits.
    Friday, March 12, 2010 5:44 PM
  • Okay, I've tried your suggestion.
    I create a bitmap object, draw my graph onto it but it's doesn't seem to make any difference. The display is still very slow when you have a large (e.g. 100,000 x,y,z's) amount of data.



    Obtain your Graphics object from the Bitmap instance, or an Image object in memory not a control.  It is much faster to draw to memory than it is to draw to a control that is being displayed to the user.  Typically, 10-20 times faster.

    Mark the best replies as answers. "Fooling computers since 1971."
    Friday, March 12, 2010 7:35 PM
  • Also try to set it's DoubleBuffered proprty to true.
    See also:
    http://msdn.microsoft.com/en-us/library/system.windows.forms.control.doublebuffered.aspx (Control.DoubleBuffered Property)

    The simplest way to use double buffering is to set the OptimizedDoubleBuffer control style flag on a control using the SetStyle method. Setting the OptimizedDoubleBuffer flag for a control redirects all painting for the control through a default graphics buffer, without requiring any additional code. This flag is set to true by default.


    There is absolutely no reason that generating and displaying an 800 X 800 image each second should have any problem with the display.
    Friday, March 12, 2010 9:18 PM
  • Hi, the double buffer property on the custom control is set to true, if I read correctly, the OptimizedDoubleBuffer property is on by default. I don't have access to the code today as its on my work pc. Essentially I created a bitmap using Bitmap thisPlot, so its not an imagte displayed on the screen i.e. all the drawing is happening in memory. What does Bitmap.LockBits do ? 
    Thanks
    Haavikon
    Saturday, March 13, 2010 2:37 PM
  • Hi, the double buffer property on the custom control is set to true, if I read correctly, the OptimizedDoubleBuffer property is on by default. I don't have access to the code today as its on my work pc. Essentially I created a bitmap using Bitmap thisPlot, so its not an imagte displayed on the screen i.e. all the drawing is happening in memory. What does Bitmap.LockBits do ? 
    Thanks
    Haavikon

    This doesn't seem right.  It takes as long to display a bitmap that you drew in memory as it did to display the plot directly onto screen?
    Saturday, March 13, 2010 3:23 PM
  • Humm, I'll post the full code on Monday when I have access. Part of my previous post has vanished which explained that it may not have been clear from the first post that I need to update this image every second or so, so I need redraw times much faster than that. The only real difference I noted between the on screen and in memory draw was then when I loaded in the data to the array, that in on case the image took a second to display, in the other it appeared in a 'sort of' wipe across the screen. 
    Saturday, March 13, 2010 8:25 PM
  • Humm, I'll post the full code on Monday when I have access. Part of my previous post has vanished which explained that it may not have been clear from the first post that I need to update this image every second or so, so I need redraw times much faster than that. The only real difference I noted between the on screen and in memory draw was then when I loaded in the data to the array, that in on case the image took a second to display, in the other it appeared in a 'sort of' wipe across the screen. 

    You're displaying an 800 X 800 bitmap.  You should never see it being displayed.  Displaying an 800 X 800  bitmap with once a second refresh should not be taxing unless you computer is more than 15 years old.  Any system with Win2K and a P4 or later should handle your program with ease.

    What version of the framework are you using?

    I take that all back.  Needs more work.  Can be visible and annoying.  Try this code:

    using System;
    using System.Diagnostics;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.Windows.Forms;
    using System.Runtime.InteropServices;

    namespace WindowsFormsApplication1
    {
      
    public partial class Form1 : Form
      {
        
    private Timer Tmr = new Timer();
        
    private PictureBox PB = new PictureBox();
        
    private int[] B;
        
    private Random Rndm = new Random();
        
    private GCHandle GCH;
        
    private Stopwatch SW = new Stopwatch();
        
    public Form1()
        {
          InitializeComponent();
          Tmr.Tick += Tmr_Tick;
          Tmr.Interval = 1000;
          PB.Image = 
    new Bitmap(800, 800);
          B = 
    new int[800 * 800];
          GCH = 
    GCHandle.Alloc(B, GCHandleType.Pinned);
          PB.Image = 
    new Bitmap(800, 800, 3200, PixelFormat.Format32bppPArgb, GCH.AddrOfPinnedObject());
          PB.SizeMode = 
    PictureBoxSizeMode.AutoSize;
          PB.Parent = 
    this;
          
    this.Width = PB.Right + 8;
          
    this.Height = PB.Bottom + 26;
          Tmr.Start();
        }
        
    public void Tmr_Tick(object sender, EventArgs e)
        {
          SW.Reset(); SW.Start();
          
    int X = 0;
          
    int Y = 0;
          
    int YW = 0;
          
    for (Y = 0; Y < PB.Height; Y++)
          {
            YW = Y * PB.Width;
            
    for (X = 0; X < PB.Width; X++)
            {
              B[YW + X] = Rndm.Next();
            }
          }
          SW.Stop(); 
    Console.WriteLine(SW.ElapsedMilliseconds);
          SW.Reset(); SW.Start();
          PB.Refresh();
          SW.Stop(); 
    Console.WriteLine(SW.ElapsedMilliseconds);
        }
      }
    }


    Drawing the bitmap takes 33 milliseconds and refreshing the PictureBox takes 9 milliseconds.
    • Marked as answer by Figo Fei Monday, March 15, 2010 9:32 AM
    • Unmarked as answer by Haavikon Monday, March 15, 2010 1:41 PM
    Saturday, March 13, 2010 8:43 PM
  • Hi Haavikon.

    Just a couple of related questions about the code you've shown and the requirements for creating your image.

    The first (and main) one is - are the points always drawn as single pixel points?

    The reason I ask is that you draw them using a circle (the line gfx.DrawEllipse(etc ...)). Also, you have zoom code in your example which isn't used in the actual drawing code. So, although the example draws a point as one pixel, depending on the properties of the gfx object, this may not necessarily be the case.

    The second one is - do you have a finite set of colours that you use for colouring the points?

    For example, are you displaying different z-values using something like a greyscale or maybe a 'hot->cold' red-blue gradient of colours, or something similar.

    In particular, if the answer to the first question is yes (i.e. one point always maps to only one pixel), then the image generation can be speeded up many times (I tried and got about a factor of 5 increase).

    This can be achieved relatively easily by writing directly to a bitmap's data set (as John suggested using the Bitmap.LockBits method) then drawing the bitmap on to the control surface.

    I have the test code I wrote, but haven't included it here for now in case you need to draw each point as a circle, in which case the solution is more complex (but still readily achievable).
    • Marked as answer by Figo Fei Monday, March 15, 2010 9:32 AM
    • Unmarked as answer by Haavikon Monday, March 15, 2010 1:41 PM
    Sunday, March 14, 2010 12:12 AM
  • using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Drawing;
    using System.Data;
    using System.Text;
    using System.Windows.Forms;
    
    namespace ClassLibrary1
    {
    
    
        public partial class XYZPlot : UserControl
        {
    
            private float majorGridLineSpacing;
            [DefaultValue(10)]
            public float MajorGridLineSpacing
            {
                set{
                    majorGridLineSpacing = value;
                    this.Invalidate();
                }
                get{
                    return majorGridLineSpacing;
                }
            }
    
            private float tickSpacing;
            [DefaultValue(5)]
            public float TickSpacing
            {
                set{
                    tickSpacing = value;
                    this.Invalidate();
                }
                get{
                    return tickSpacing;
                }
            }
            
            private float tickHeight;
            [DefaultValue(.5)]
            public float TickHeight
            {
                get
                {
                    return tickHeight;
                }
                set
                {
                    tickHeight = value;
                    this.Invalidate();
                }
            }
    
            private float zoomRadius;
            [DefaultValue(40)]
            public float ZoomRadius
            {
                set
                {
                    zoomRadius = value;
                    SetMaxMin();
                    this.Invalidate();
    
                }
                get
                {
                    return zoomRadius;
                }
            }
            
            private float zoomXCentre;
            [DefaultValue(0)]
            public float ZoomXCentre
            {
                set
                {
                    zoomXCentre = value;
                    SetMaxMin();
                    this.Invalidate();
                }
                get
                {
                    return zoomXCentre;
                }
            }
         
            private float zoomYCentre;
            [DefaultValue(0)]
            public float ZoomYCentre
            {
                set
                {
                    zoomYCentre = value;
                    SetMaxMin();
                    this.Invalidate();
                }
                get
                {
                    return zoomYCentre;
                }
            }
    
            private ROI regionOfInterest = new ROI();
            public ROI RegionOfInterest
            {
                get
                {
                    return regionOfInterest;
                }
                set
                {
                    regionOfInterest = value;
                    this.Invalidate();
                }
            }
    
            private Bitmap thisPlot = new Bitmap(100,100);
            private float xmin;
            private float xmax;
            private float ymin;
            private float ymax;
            private int graphMargin=0;
            private float margin;
            private float x0;
            private float y0;
            private float width;
            private float height;
            private float xscale;
            private float yscale;
            private int[,] z = new int[800,800];
            private int[,] zold = new int[800, 800];
            private int maxZ;
            
            public XYZPlot()
            {
                InitializeComponent();
            }
    
            private void SetMaxMin()
            {
                xmin = (int)(zoomXCentre - zoomRadius);
                xmax = (int)(zoomXCentre + zoomRadius);
                ymin = (int)(zoomYCentre - zoomRadius);
                ymax = (int)(zoomYCentre + zoomRadius);
                xscale = width / (2 * zoomRadius);
                yscale = height / (2 * zoomRadius);
            }
    
            public void ClearPoints()
            {
                z.Initialize();
                maxZ = 10;
            }
    
            public void AddPoint(float X, float Y)
            {
                int xindex, yindex;
                xindex = (int)(X / 0.1)+400;
                yindex = (int)(Y / 0.1)+400;
                if ((xindex < 800) & (xindex > -1) & (yindex < 800) & (yindex > -1))
                {
                    z[xindex, yindex]++;
                    if (z[xindex, yindex] > maxZ + 10)
                    {
                        maxZ = z[xindex,yindex];
                    }
                }
                base.Invalidate();
            }
    
            public void PlotArray(int[,] thisArray, int maxValue)
            {
                if (thisArray.Length == (800 * 800))
                {
                    zold = z;
                    z = thisArray;
                    maxZ = maxValue;
                    this.Invalidate();
                }
            }
    
            private void PlotPoints(PaintEventArgs PaintEvnt)
            {
                //Graphics gfx = PaintEvnt.Graphics;
                Graphics gfx = Graphics.FromImage(thisPlot);
                Pen myPen = new Pen(Color.Black, 1f);
                double Y;
                double X;
                int ximin = (int)((xmin/0.1) + 400);
                int ximax = (int)((xmax / 0.1) + 400);
                int yimin = (int)((ymin / 0.1) + 400);
                int yimax = (int)((ymax / 0.1) + 400);
                Point myPoint = new Point();
                if (ximin < 0)
                    ximin = 0;
                if (ximax > 800)
                    ximax = 800;
                if (yimin < 0)
                    yimin = 0;
                if (yimax > 800)
                    yimax = 800;
    
                for (int xindex = ximin; xindex < ximax; xindex++)
                {
                    for (int yindex = yimin; yindex < yimax; yindex++)
                    {
                        if ((z[xindex, yindex] > 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                        {
                            X = ((double)xindex * 0.1) - 40;
                            Y = ((double)yindex * 0.1) - 40;
                            myPoint = PositionToPixels(X, Y);
                            myPen.Color = GetColor(z[xindex, yindex]);
                            gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                        }
                        if ((z[xindex, yindex] == 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                        {
                            X = ((double)xindex * 0.1) - 40;
                            Y = ((double)yindex * 0.1) - 40;
                            myPoint = PositionToPixels(X, Y);
                            myPen.Color = Color.Black;
                            gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                        }
                    }
                }
            }
    
            private Color GetColor(int value)
            {
                double rgbtotal;
                rgbtotal = 240 - (((double)value/(double)maxZ) * 240); 
                HSLColor thisColor = new HSLColor();
                thisColor.Hue = rgbtotal;
                thisColor.Saturation = 360;
                thisColor.Luminosity = 180; 
                return thisColor;
            }
    
            private Point PositionToPixels(double X, double Y)
            {
                double xprime = X + zoomRadius - zoomXCentre;
                double yprime = Y + zoomRadius - zoomYCentre;
                Point pixels = new Point();
                pixels.X = (int)(xprime * xscale);
                pixels.Y = (int)(height - (yprime * yscale));
                return pixels; 
            }
    
            private void SetSizeVairables()
            {
                x0 = base.Size.Width / 2;
                y0 = base.Size.Height / 2;
                margin = base.Size.Width * graphMargin / 100;
                width = base.Size.Width - 2 * margin;
                height = base.Size.Height - 2 * margin;
                xscale = width/(2*zoomRadius);
                yscale = height / (2*zoomRadius);
                thisPlot = new Bitmap(base.Size.Width, base.Size.Height);
                //zold = new Int32[800, 800];
                
            }
    
            private void XYZPlot_Resize(object sender, EventArgs e)
            {
                SetSizeVairables();
            }
    
            private void DrawDLD80Outline(PaintEventArgs PaintEvnt)
            {
                //Graphics gfx = PaintEvnt.Graphics;
                Graphics gfx = Graphics.FromImage(thisPlot);
                Pen myPen = new Pen(Color.White,3);
                gfx.DrawEllipse(myPen, 0, 0, width,height);
                myPen.Dispose();
            }
    
            private void DrawGridLines(PaintEventArgs PaintEvnt)
            {
                //Graphics gfx = PaintEvnt.Graphics;
                Graphics gfx = Graphics.FromImage(thisPlot);
                //Arrr' here be a pen to draw with 
                Pen myPen = new Pen(Color.White,1f);
                //We'll be needing these points to draw some lines arrr'
                Point myPoint1 = new Point();
                Point myPoint2 = new Point();
                Point myPoint3 = new Point();
                Point myPoint4 = new Point();
                Point myPoint5 = new Point();
                Point myPoint6 = new Point();
    
                myPoint1 = PositionToPixels(zoomXCentre, ymax);
                myPoint2 = PositionToPixels(zoomXCentre, ymin);
                myPen.Color = Color.White;
                myPen.Width = .5f;
                gfx.DrawLine(myPen, myPoint1, myPoint2);
                //XAxis
                myPoint1 = PositionToPixels(xmin, zoomYCentre);
                myPoint2 = PositionToPixels(xmax, zoomYCentre);
                myPen.Color = Color.White;
                myPen.Width = .5f;
                gfx.DrawLine(myPen, myPoint1, myPoint2);
                
                //Course XY grid 
                for (float spacing = xmin; spacing < xmax; spacing = spacing + majorGridLineSpacing)
                {
                    myPoint3 = PositionToPixels(spacing, ymin);
                    myPoint4 = PositionToPixels(spacing, ymax);
                    myPoint5 = PositionToPixels(spacing, zoomYCentre);
                    myPen.Color = Color.Gray;
                    myPen.Width = 0.25f;
                    gfx.DrawString(spacing.ToString(), new Font("Arial", 8), Brushes.White, myPoint5);
                    gfx.DrawLine(myPen, myPoint3, myPoint4);
      
                }
    
                for (float spacing = ymin; spacing < ymax; spacing = spacing + majorGridLineSpacing)
                {
                    myPoint1 = PositionToPixels(xmin, spacing);
                    myPoint2 = PositionToPixels(xmax, spacing);
                    myPoint6 = PositionToPixels(zoomXCentre, spacing);
                    myPen.Color = Color.Gray;
                    myPen.Width = 0.25f;
                    gfx.DrawLine(myPen, myPoint1, myPoint2);
                    gfx.DrawString(spacing.ToString(), new Font("Arial", 8), Brushes.Cyan, myPoint6);
                }
    
                //Axis Ticks
                for (float spacing = xmin; spacing < xmax; spacing = spacing + tickSpacing)
                {
                    myPoint3 = PositionToPixels(spacing, zoomYCentre - tickHeight);
                    myPoint4 = PositionToPixels(spacing, zoomYCentre + tickHeight);
                    myPen.Color = Color.Gray;
                    myPen.Width = 0.25f;
                    gfx.DrawLine(myPen, myPoint3, myPoint4);
                }
    
                for (float spacing = ymin; spacing < ymax; spacing = spacing + tickSpacing)
                {
                    myPoint1 = PositionToPixels(zoomXCentre - tickHeight, spacing);
                    myPoint2 = PositionToPixels(zoomXCentre + tickHeight, spacing);
                    myPen.Color = Color.Gray;
                    myPen.Width = 0.25f;
                    gfx.DrawLine(myPen, myPoint1, myPoint2);
                } 
    
            }
    
            private void XYZPlot_Paint(object sender, PaintEventArgs e)
            {
                PlotPoints(e);
                DrawGridLines(e);
                DrawROI(e);
                DrawDLD80Outline(e);
                e.Graphics.DrawImage(thisPlot, new Point(0,0));
            }
    
            private void XYZPlot_Load(object sender, EventArgs e)
            {
                ClearPoints();
                SetSizeVairables();
            }
    
            private void DrawROI(PaintEventArgs PaintEvnt)
            {
                if (regionOfInterest.EnableROI)
                {
                    //Graphics gfx = PaintEvnt.Graphics;
                    Graphics gfx = Graphics.FromImage(thisPlot);
                    Pen myPen = new Pen(Color.Blue,1f);
                    Point myPoint = new Point();
                    myPoint = PositionToPixels(regionOfInterest.X0, regionOfInterest.Y0);
                    
                    float roiwidth = regionOfInterest.MaximumRadius * 2 * xscale;
                    float roiheight = regionOfInterest.MaximumRadius * 2 * yscale;
    
                    gfx.DrawEllipse(myPen, (myPoint.X - roiwidth/2), (myPoint.Y - roiheight/2), roiwidth, roiheight);
                    myPen.Color = Color.Red;
                    
                    roiwidth = regionOfInterest.MinimumRadius * 2 * xscale;
                    roiheight = regionOfInterest.MinimumRadius * 2 * yscale;
    
                    gfx.DrawEllipse(myPen, (myPoint.X - roiwidth / 2), (myPoint.Y - roiheight / 2), roiwidth, roiheight);
                    myPen.Dispose();
                }
            }
        }
    }
    
    
    namespace ClassLibrary1
    {
        public class ROITypeConverter : TypeConverter
        {
            public ROITypeConverter()
            {
            }
            public override bool GetPropertiesSupported(ITypeDescriptorContext context)
            {
                return true;
            }
            public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
            {
                return TypeDescriptor.GetProperties(typeof(ROI));
            }
    
    
        }
    
        [TypeConverter(typeof(ROITypeConverter))]
        public class ROI
        {
            private float x0;
            [DefaultValue(0)]
            public float X0
            {
                get
                {
                    return x0;
                }
                set
                {
                    x0 = value;
                }
            }
            private float y0;
            [DefaultValue(0)]
            public float Y0
            {
                get
                {
                    return y0;
                }
                set
                {
                    y0 = value;
                }
            }
            private float minimumRadius;
            [DefaultValue(0)]
            public float MinimumRadius
            {
                get
                {
                    return minimumRadius;
                }
                set
                {
                    minimumRadius = value;
                }
            }
            private float maximumRadius;
            [DefaultValue(10)]
            public float MaximumRadius
            {
                get
                {
                    return maximumRadius;
                }
                set
                {
                    maximumRadius = value;
                }
            }
    
            private bool enableROI;
            [DefaultValue(false)]
            public bool EnableROI
            {
                get
                {
                    return enableROI;
                }
                set
                {
                    enableROI = value;
                }
            }
        }
    
    
     public class HSLColor
         {
             // Private data members below are on scale 0-1
             // They are scaled for use externally based on scale
             private double hue = 1.0;
             private double saturation = 1.0;
             private double luminosity = 1.0;
      
             private const double scale = 360.0;
      
             public double Hue
             {
                 get { return hue * scale; }
                 set { hue = CheckRange(value / scale); }
             }
             public double Saturation
             {
                 get { return saturation * scale; }
                 set { saturation = CheckRange(value / scale); }
             }
             public double Luminosity
             {
                 get { return luminosity * scale; }
                 set { luminosity = CheckRange(value / scale); }
             }
      
             private double CheckRange(double value)
             {
                 if (value < 0.0)
                     value = 0.0;
                 else if (value > 1.0)
                     value = 1.0;
                 return value;
             }
      
             public override string ToString()
             {
                 return String.Format("H: {0:#0.##} S: {1:#0.##} L: {2:#0.##}",   Hue, Saturation, Luminosity);
             }
      
             public string ToRGBString()
             {
                 Color color = (Color)this;
                 return String.Format("R: {0:#0.##} G: {1:#0.##} B: {2:#0.##}", color.R, color.G, color.B);
             }
      
             #region Casts to/from System.Drawing.Color
             public static implicit operator Color(HSLColor hslColor)
             {
                 double r = 0, g = 0, b = 0;
                 if (hslColor.luminosity != 0)
                 {
                     if (hslColor.saturation == 0)
                         r = g = b = hslColor.luminosity;
                     else
                     {
                         double temp2 = GetTemp2(hslColor);
                         double temp1 = 2.0 * hslColor.luminosity - temp2;
      
                         r = GetColorComponent(temp1, temp2, hslColor.hue + 1.0 / 3.0);
                         g = GetColorComponent(temp1, temp2, hslColor.hue);
                         b = GetColorComponent(temp1, temp2, hslColor.hue - 1.0 / 3.0);
                     }
                 }
                 return Color.FromArgb((int)(255 * r), (int)(255 * g), (int)(255 * b));
             }
      
             private static double GetColorComponent(double temp1, double temp2, double temp3)
             {
                 temp3 = MoveIntoRange(temp3);
                 if (temp3 < 1.0 / 6.0)
                     return temp1 + (temp2 - temp1) * 6.0 * temp3;
                 else if (temp3 < 0.5)
                     return temp2;
                 else if (temp3 < 2.0 / 3.0)
                     return temp1 + ((temp2 - temp1) * ((2.0 / 3.0) - temp3) * 6.0);
                 else
                     return temp1;
             }
             private static double MoveIntoRange(double temp3)
             {
                 if (temp3 < 0.0)
                     temp3 += 1.0;
                 else if (temp3 > 1.0)
                     temp3 -= 1.0;
                 return temp3;
             }
             private static double GetTemp2(HSLColor hslColor)
             {
                 double temp2;
                 if (hslColor.luminosity < 0.5)  //<=??
                     temp2 = hslColor.luminosity * (1.0 + hslColor.saturation);
                 else
                     temp2 = hslColor.luminosity + hslColor.saturation - (hslColor.luminosity * hslColor.saturation);
                 return temp2;
             }
      
             public static implicit operator HSLColor(Color color)
             {
                 HSLColor hslColor = new HSLColor();
                 hslColor.hue = color.GetHue() / 360.0; // we store hue as 0-1 as opposed to 0-360 
                 hslColor.luminosity = color.GetBrightness();
                 hslColor.saturation = color.GetSaturation();
                 return hslColor;
             }
             #endregion
      
             public void SetRGB(int red, int green, int blue)
             {
                 HSLColor hslColor = (HSLColor)Color.FromArgb(red, green, blue);
                 this.hue = hslColor.hue;
                 this.saturation = hslColor.saturation;
                 this.luminosity = hslColor.luminosity;
             }
      
             public HSLColor() { }
             public HSLColor(Color color)
             {
                 SetRGB(color.R, color.G, color.B);
             }
             public HSLColor(int red, int green, int blue)
             {
                 SetRGB(red, green, blue);
             }
             public HSLColor(double hue, double saturation, double luminosity)
             {
                 this.Hue = hue;
                 this.Saturation = saturation;
                 this.Luminosity = luminosity;
             }
      
      
         }
     }
    Full code for the control, after this discussion I can see I'm doing some things the long way around!
    Monday, March 15, 2010 1:44 PM
  • Hi Chris,
    Yes after the discussion above, I get the feeling that drawing a circle isn't the way to go with this and there is no reason to do so. I've posted the full code above and I have a feeling my lack of experience in graphics coding will shine through. I'll try and implement some of the ideas above asap and see how it goes.
    Monday, March 15, 2010 1:47 PM
  • Hi Haavikon,

    As far as I can see from your code, you do only ever plot one point as one pixel.

    If that's right and I've understood the problem properly, then I think if you modify your points plotting method as below, it should be faster.

    The code uses the lock bits method. All it does is what you do now, but by writing directly to a data array - 'bitmapArray' - instead of using the Graphics methods. This array has the same format that the actual bitmap uses, so once it is created, it is copied to and therefore replaces the bitmap's own internal array. (The code assumes the bitmap is in 32 bits per pixel argb format).

    // Add this somewhere.
    Byte[] bitmapArray = new Byte[1];
    
    private void PlotPoints(PaintEventArgs PaintEvnt)
    {
        //Graphics gfx = PaintEvnt.Graphics;
    
        // Don't need this here.
        // Graphics gfx = Graphics.FromImage(thisPlot);
        Pen myPen = new Pen(Color.Black, 1f);
        double Y;
        double X;
        int ximin = (int)((xmin / 0.1) + 400);
        int ximax = (int)((xmax / 0.1) + 400);
        int yimin = (int)((ymin / 0.1) + 400);
        int yimax = (int)((ymax / 0.1) + 400);
        Point myPoint = new Point();
        if (ximin < 0)
            ximin = 0;
        if (ximax > 800)
            ximax = 800;
        if (yimin < 0)
            yimin = 0;
        if (yimax > 800)
            yimax = 800;
    
    
        // 'Lock' the bitmap's data.
        BitmapData bitmapData = thisPlot.LockBits(new Rectangle(0, 0, thisPlot.Width, thisPlot.Height),
            ImageLockMode.ReadWrite, thisPlot.PixelFormat);
    
        // The stride is the number of bytes per row in the bitmapData.
        int stride = bitmapData.Stride;
    
        Color pixelColor;
        int pixelIndex;
    
        // Check the bitmapArray is big enough.
        int requiredSize = stride * thisPlot.Height;
    
        if (bitmapArray.Length < requiredSize)
        {
            bitmapArray = new Byte[requiredSize];
        }
        
        for (int xindex = ximin; xindex < ximax; xindex++)
        {
            for (int yindex = yimin; yindex < yimax; yindex++)
            {
                if ((z[xindex, yindex] > 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                {
                    X = ((double)xindex * 0.1) - 40;
                    Y = ((double)yindex * 0.1) - 40;
                    myPoint = PositionToPixels(X, Y);
    
                    // Check that the point is within the bounds of the bitmap.
                    if ((myPoint.X >= 0 && myPoint.X < thisPlot.Width) &&
                        (myPoint.Y >= 0 && myPoint.Y < thisPlot.Height))
                    {
                        // Get the index of the point in the bitmapArray.
                        pixelIndex = (myPoint.Y * stride) + (myPoint.X * 4);
    
                        // Get the colour needed.
                        pixelColor = GetColor(z[xindex, yindex]);
    
                        // Now fill the pixel bytes in. The order is Blue, Green, Red, Alpha.
                        bitmapArray[pixelIndex + 0] = pixelColor.B;
                        bitmapArray[pixelIndex + 1] = pixelColor.G;
                        bitmapArray[pixelIndex + 2] = pixelColor.R;
                        bitmapArray[pixelIndex + 3] = pixelColor.A;
                    }
    
                    //myPen.Color = GetColor(z[xindex, yindex]);
                    //gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                }
                if ((z[xindex, yindex] == 0) & (z[xindex, yindex] != zold[xindex, yindex]))
                {
                    X = ((double)xindex * 0.1) - 40;
                    Y = ((double)yindex * 0.1) - 40;
                    myPoint = PositionToPixels(X, Y);
    
                    
                    // Likewise for the zero points.
                    if ((myPoint.X >= 0 && myPoint.X < thisPlot.Width) &&
                        (myPoint.Y >= 0 && myPoint.Y < thisPlot.Height))
                    {
                        // Get the index of the point in the bitmapArray.
                        pixelIndex = (myPoint.Y * stride) + (myPoint.X * 4);
    
                        bitmapArray[pixelIndex + 0] = 0; // -----
                        bitmapArray[pixelIndex + 1] = 0; // Black
                        bitmapArray[pixelIndex + 2] = 0; // -----
                        bitmapArray[pixelIndex + 3] = 255;
                    }
    
                    //myPen.Color = Color.Black;
                    //gfx.DrawEllipse(myPen, myPoint.X, myPoint.Y, 1, 1);
                }
            }
        }
    
        // Done the drawing, so now copy the data back to the bitmap again.
        // This line copies the managed array (bitmapArray) to the
        // unmanaged bitmapData array.
        Marshal.Copy(bitmapArray, 0, bitmapData.Scan0, requiredSize);
    
        // Unlock the bitmap.
        thisPlot.UnlockBits(bitmapData);
    }
    • Proposed as answer by Figo Fei Wednesday, March 17, 2010 1:26 AM
    • Marked as answer by Figo Fei Thursday, March 18, 2010 1:27 AM
    Monday, March 15, 2010 5:00 PM