Editing Bitmap objects while on screen

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • haven1433
    New Member
    • May 2010
    • 5

    Editing Bitmap objects while on screen

    I'm writing an application that draws custom fractal images, so I'm currently creating an empty bitmap, drawing to that, and then drawing that on the screen using a form's OnPaint. This lets me do quick transformations like rotations and panning while still calculating the fractal. I want to update the bitmap while its on the screen, so the fractal updates itself even while you're panning or rotating.

    Problem is, Visual Studio gets really angry (I don't remember the exact error) if I try to call SetPixel() while the bitmap is on the screen. How do I get around this? Is there another Image subclass I should use, or a buffer I need to clear? Java does this easily with the BufferedImage class. Is there a similar functionality in C#?

    Thanks,

    haven1433
  • GaryTexmo
    Recognized Expert Top Contributor
    • Jul 2009
    • 1501

    #2
    I just did a test where I was calling SetPixel on a bitmap that was on screen and I'm not having any troubles... is there possibly more to your problem?

    Code:
        public partial class Form1 : Form
        {
            private Bitmap m_bmp = new Bitmap(100, 100);
            private Color m_drawColor = Color.Blue;
            private ColorDialog m_colorDialog = new ColorDialog();
    
            public Form1()
            {
                InitializeComponent();
               
                pictureBox1.Image = m_bmp;
                m_colorDialog.Color = m_drawColor;
                UpdateColor();
            }
    
            public void UpdateColor()
            {
                button1.BackColor = m_drawColor;
                button1.ForeColor = Color.FromArgb(255 - m_drawColor.R, 255 - m_drawColor.G, 255 - m_drawColor.B);
    
                for (int x = 0; x < m_bmp.Width; x++)
                    for (int y = 0; y < m_bmp.Height; y++)
                        m_bmp.SetPixel(x, y, m_drawColor);
    
                pictureBox1.Invalidate();
                pictureBox1.Update();
            }
    
            private void button1_Click(object sender, EventArgs e)
            {
                if (m_colorDialog.ShowDialog() == DialogResult.OK)
                {
                    m_drawColor = m_colorDialog.Color;
                    UpdateColor();
                }
            }
        }
    If you can, post an example program that demonstrates the error and perhaps we can help!

    Comment

    • haven1433
      New Member
      • May 2010
      • 5

      #3
      Here's a code example:

      I've left a lot of details in, so here is the jist.

      First of all, the program assumes that the form has a single button somewhere tied to button1_Click. It creates a form (preferably large) and then waits around, doing nothing. The form is set to refresh itself every 10 seconds, though really I'm more interested in it refreshing every .1 seconds: I just dialed up the value for the example.

      Once you push the button, a worker frantically makes a fractal as fast as it can and the refresh paints it at the specified interval. The error occurs when I try to draw it to the screen and update the bitmap pixels at the same time. But I want to be able to update the image often, showing more image as it's completed. I also don't want to force the user to wait until the entire fractal is drawn to see it or force the SetPixel() to pause while drawing. Is there a thread-safe way to do this without adversely effecting performance?

      Code:
      public partial class Form1 : Form {
              
              private int x2;
              private int y2;
              private int iter_max;
              private Bitmap bm = null;//new Bitmap(DisplayRectangle.Width, DisplayRectangle.Height);
              private double bailout;
      
              private Timer update;
      
      
              public Form1() {
                  InitializeComponent();
      
                  //create the bitmap
                  bm = new Bitmap(DisplayRectangle.Width, DisplayRectangle.Height);
                  
      
                  //init other constants
                  x2 = DisplayRectangle.Width / 2;
                  y2 = DisplayRectangle.Height / 2;
                  iter_max = 50;
                  bailout = 1000000;
      
                  //create a timer to update the form at 10fps
                  update = new Timer();
                  update.Tick += new EventHandler(update_Tick);
                  update.Interval = 10000;
                  update.Enabled = true;
                  
                  
              }
      
              void update_Tick(object sender, EventArgs e) {
                  Refresh();
              }
      
              void  DoWork(object sender, DoWorkEventArgs e){
       	        Run();
              }
              
      
      
              public void Run() {
      
                  double zoom = .0045; // smaller number = bigger fractal
      
                  for (int x = 0; x < DisplayRectangle.Width; x++){
                      for (int y = 0; y < DisplayRectangle.Height; y++) {
      
                          int iter = 0;
                          double zr = (x2 - x) * zoom, zi = (y - y2) * zoom;
                          double cr = .5, ci = .25;
                          
                          //iterative formula
                          while (iter < iter_max && Math.Sqrt(zr*zr+zi*zi) < bailout) {
                              
                              double a = zr * zr - zi * zi;
                              double b = zr * zi * 2;
                              zr = a + cr;
                              zi = b + ci;
                              iter++;
      
                          }
                          
                          //color formula
                          double r = Math.Sqrt(zr * zr + zi * zi);
                          double d = 1 + Math.Log(Math.Log(bailout) / Math.Log(r)) / Math.Log(2);
                          
                          double colVal = (d + iter) / (iter_max + 1);
                          colVal = Math.Abs(colVal - .5) * 2;
                          
                          byte val = (byte)(colVal * 255);
                          if (r < bailout) val = 0;
                          
                          //set a pixel according to the color formula
                          bm.SetPixel(x, y, Color.FromArgb(val,val,val));
                      }
                  }
              }
      
              
              protected override void OnPaint(PaintEventArgs e) {
       	        base.OnPaint(e);
                  if (bm == null) return;
      
                  e.Graphics.DrawImage(bm, new Point(0, 0));
      
              }
      
              private void button1_Click(object sender, EventArgs e) {
                  BackgroundWorker worker = new BackgroundWorker();
                  worker.DoWork += new DoWorkEventHandler(DoWork);
                  worker.RunWorkerAsync();
              }
      
          }

      Comment

      • GaryTexmo
        Recognized Expert Top Contributor
        • Jul 2009
        • 1501

        #4
        The exception I got was "Object currently in use" which suggests to me that the generation of the fractal doesn't finish before the next tick of the timer. Throwing a lock around the drawing stops the exception, but of course that doesn't get you the desired result ;)

        I have an idea of what you might be able to do here but I need to finish up something and then play around. I'll get back to you!

        *Edit: Ok, so I'm dumb :D I was envisioning elaborate array access to draw when it occurred to me I could just lock when I access the variable, not for the whole draw process (as you should anyway!!).

        So, put...

        Code:
        lock (bm) { ... }
        ... around both your SetPixel call and your DrawImage call and you should actually be golden. Then you can set the update interval to whatever you like.

        That said, WinForms drawing isn't exactly known for it's speediness! On my machine, I'd get form flicker on update intervals less than 1000 milliseconds. But this is fairly easy to get around... create a bool and an object (for a lock since you can only lock on reference types), then set the flag to true when you're finished generating the fractal. In the next update, if the bool is true, change the interval to something higher (like 1000 milliseconds, or more if you want)

        Comment

        • haven1433
          New Member
          • May 2010
          • 5

          #5
          Faster Drawing

          Thanks for the response, and sorry I wasn't able to test your solution earlier. You're right - using the lock() clears up the access / modification problem I'm having. Unfortunately, the image isn't going to be static once I draw it to the screen, so I still need a way to draw it quickly.

          I'm still new to C#, but I've used Java for quite a while, which is why I brought up BufferedImage as an example. Once the image is drawn, it would be nice to dial back the refresh timer as per your suggestion. However, I plan on implementing commands allowing the user to grab and drag or rotate the image around the screen, and I would like the image to move as the user drags it. Obviously this requires a rather speedy update rate, and flickering would be ... bad.

          I suppose drawing a Bitmap to a Form wouldn't work then. What would be a better way to accomplish this? Is there an easy way to do double buffering on the Form's drawing surface?

          Comment

          • GaryTexmo
            Recognized Expert Top Contributor
            • Jul 2009
            • 1501

            #6
            Why don't you try XNA? You can just draw your fractal right in the default XNA window, or you can embed an XNA panel in a windows form so you can have buttons and such too.

            It's actually fairly easy to deal with, though there are a few quirks. For instance, it's not really meant for drawing 2D, per se... the way one would normally draw 2D would be to stamp Texture2D objects as sprites. However, a Texture2D object is simply an array of Color data, so it's quite possible to manipulate it on the fly. XNA will take care of the update and draw loops automatically and you can always write logic to maximize the amount of time you have available in that update loop, though I'm pretty sure a 100x100 buffer will process in no time.

            Have a look around google for some basic tutorials and feel free to ask me if you need any help. All of my XNA experience is in 2D so hopefully I can help you out ;)

            Comment

            Working...