Visual C# Developer Center > Visual C# Forums > Visual C# General > using DrawImage on a pictureBox but not image?
Ask a questionAsk a question
 

Answerusing DrawImage on a pictureBox but not image?

  • Saturday, October 31, 2009 8:56 PMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    hi,
    i've got a pictureBox which displays the map of a war-game.  this map is drawn with many calls to DrawImage to draw the different squares of a grid. 
    but the pictureBox's image is never set and remains null.  my creategraphics calls is like this :


    gr = picMap.CreateGraphics();
    
    where picMap is the picturebox mentioned above.  the map is scrolled in and out of the user's view and when it returns from being drawn off the screen or behind an object like a label the map is destroyed and picMap.Image remains Null.
    how do I draw onto the picturebox's image?

    and what does this :

    gr.DrawImage(imgTree, intX, intY, intWidth, intHeight);
    
    actually do if I can see this 'tree' at point (intX, intY) and of size (intWidth, intHeight) if my picturebox picMap has no image?

    BadButBit
    my code is perfect until i don't find a bug

Answers

  • Sunday, November 01, 2009 3:54 AMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Well, you can just create a bitmap that is as large as the entire map, and assign that to your picturebox.
    At this point, all you have to do is to create a Graphics object from the bitmap:

    assuming you have:

    Bitmap entireMap = new Bitmap (...); // make the bitmap large enough to contain all of your tiles.
    picMap.Image = entireMap;

    to paint on the bitmap you can just create an appropriate Graphics object:

    using (Graphics g = Graphics.FromImage (entireMap)) {
      g.DrawImage (...); // draw the tile at the appropriate coordinates
      ... // more drawing if needed
    }

    In this case, there is no need to implement the Paint event: everything will be taken care of automatically. The only thing to remember is that whenever you draw to the bitmap you must also invalidate the control (i.e. picMap) to make sure that the screen is redrawn using the updated bitmap.

    Frankly, I would not recommend this approach, as you are going to use a very large amount of memory... especially if the game map is much larger than the screen.
    Painting on the fly is somewhat less efficient in terms of CPU (you make more GDI+ calls), but it will keep your memory way down, especially so if your maps contain several tiles with the same bitmap.
    In a nutshell, painting on the fly allows you to use almost arbitrarily large maps.

    Also, consider what happens if some tiles are animated or change during the game... consider a "water effect", for instance:
    If you use a single large bitmap, every few millisecons you have to go and change every water tile to achieve the animation. Painting on the fly, instead, you just go changing the indices in the tile map, or just swap the water tile and invalidate. The difference is enormous.

    Anyway, that's your call...

    HTH
    --mc
    • Marked As Answer byBadButBit Sunday, November 01, 2009 6:08 AM
    •  

All Replies

  • Saturday, October 31, 2009 10:11 PMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    You don't have to. You can simply react to the Paint event, which passes you a Graphics object to use for drawing.

    HTH
    --mc
  • Saturday, October 31, 2009 10:32 PMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    thanks for your response but i'm not sure what you're saying after trying this :

    void picMap_Paint(object sender, PaintEventArgs e)
    {
        PictureBox thisPic = (PictureBox)sender;
    
        thisPic.Image = ???
    }
    
    clearly  I am missing something?  could you elaborate?

    BadButBit
    my code is perfect until i don't find a bug
  • Sunday, November 01, 2009 1:32 AMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    What I mean is to draw using e.Graphics.

    The entire concept of Windows is that you don't need to store the appearance of a window anywhere: whenever a portion of a window (or a control, which is usually a child window) needs to be repainted, you get a Paint event that requires your application to redraw that area.

    Now, I don't know if you are using a grid of pictureboxes (i.e. each picturebox contains a single tile of the game) or you have a single picturebox containing all the tiles.
    Since I would advise the latter approach, and it is also a little more complex, that is the one I'm going to show.

    All you need to do is to in your Paint event is:

    void picMap_Paint (object sender, PaintEventArgs e) {

      // iterate through all the tiles that are currently shown in the picturebox (accounting for scrolling, zooming and the like).
      // Assuming you have appropriate variables keeping track of the first and last row shown, and first and last columns shown:

      for (int row = firstRow; row <= lastRow; row++) {
        for (int column = firstColumn; column <= lastColumn; column++) {

          // create a rectangle representing the coordinates of the tile relative to the picturebox,
          // then check if it needs repainting (you can use e.ClipRectangle to determine if that's the case)
       
          Rectangle tileRect = new Rectangle ((column - firstColumn) * tileWidth, (row - firstRow) * tileHeight, tileWidth, tileHeight);
          if (tileRect.IntersectsWith (e.ClipRectangle)) { // if this is true, we must repaint the tile

            // assuming we have an array of all the possible images (tree, sea, land...)
            // and our map contains the indices to that array, we can simply do the following:

            e.Graphics.DrawImage (images [map [row, column]], tileRect);
          }
        }
      }
    }

    The only thing to remember is that whenever a tile changes, we must invalidate the corresponding portion of the control using something like:

    picMap.Invalidate (...);

    This will force a Paint event to be raised on the control.

    The code included is pretty rough (and written directly here, so watch out for typos), but you should get the idea.

    HTH
    --mc

  • Sunday, November 01, 2009 2:45 AMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
          Rectangle tileRect = new Rectangle ((column - firstColumn) * tileWidth, (row - firstRow) * tileHeight, tileWidth, tileHeight);
          if (tileRect.IntersectsWith (e.ClipRectangle)) { // if this is true, we must repaint the tile

            // assuming we have an array of all the possible images (tree, sea, land...)
            // and our map contains the indices to that array, we can simply do the following:

            e.Graphics.DrawImage (images [map [row, column]], tileRect);
          }


    this bit of code has a couple of new tricks for me which i can put to good use.
    what i'm using is a single picBox with two scroll bars (v & h) moving it around inside a panel which docks to the form (a throw-back from when i had an array of picboxes on a panel where the single picbox is now) and i would like to NOT HAVE TO REDRAW the image everytime as if i had drawn my original map once into a bitmap image and stored it in a picturebox which i could move around.
    I'll certainly make use of what you've shown me here but is there a way to create a permanent image from scratch and store it in a picturebox as if I'd taken a screen capture of a dynamically generated map and then loaded it off a file?

    thanks for your help,
    BadButBit
    my code is perfect until i don't find a bug
  • Sunday, November 01, 2009 3:54 AMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Answer
    Well, you can just create a bitmap that is as large as the entire map, and assign that to your picturebox.
    At this point, all you have to do is to create a Graphics object from the bitmap:

    assuming you have:

    Bitmap entireMap = new Bitmap (...); // make the bitmap large enough to contain all of your tiles.
    picMap.Image = entireMap;

    to paint on the bitmap you can just create an appropriate Graphics object:

    using (Graphics g = Graphics.FromImage (entireMap)) {
      g.DrawImage (...); // draw the tile at the appropriate coordinates
      ... // more drawing if needed
    }

    In this case, there is no need to implement the Paint event: everything will be taken care of automatically. The only thing to remember is that whenever you draw to the bitmap you must also invalidate the control (i.e. picMap) to make sure that the screen is redrawn using the updated bitmap.

    Frankly, I would not recommend this approach, as you are going to use a very large amount of memory... especially if the game map is much larger than the screen.
    Painting on the fly is somewhat less efficient in terms of CPU (you make more GDI+ calls), but it will keep your memory way down, especially so if your maps contain several tiles with the same bitmap.
    In a nutshell, painting on the fly allows you to use almost arbitrarily large maps.

    Also, consider what happens if some tiles are animated or change during the game... consider a "water effect", for instance:
    If you use a single large bitmap, every few millisecons you have to go and change every water tile to achieve the animation. Painting on the fly, instead, you just go changing the indices in the tile map, or just swap the water tile and invalidate. The difference is enormous.

    Anyway, that's your call...

    HTH
    --mc
    • Marked As Answer byBadButBit Sunday, November 01, 2009 6:08 AM
    •  
  • Sunday, November 01, 2009 6:10 AMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    awesome,
    while i have your attention :
    how do i capture an image on a bitmap, store it someplace draw animation over that spot and then restore the background to its original image using the area i captured earlier?

    BadButBit
    my code is perfect until i don't find a bug
  • Sunday, November 01, 2009 1:22 PMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hmm... I'm not sure I got your question right. I guess you want to save a portion of your game map right?

    The process is not complicated, just tedious:

    1) Determine the position and size (on the global bitmap) of the area you want to save.

    Rectangle saveArea = new Rectangle (...);

    2) Create a spare bitmap with the same size (if you always save an area of the same size, just keep a bitmap around and reuse that)

    Bitmap savedBitmap = new Bitmap (saveArea.Width, saveArea.Height);

    3) Copy the portion you need:

    using (Graphics g = Graphics.FromImage (savedBitmap)) {
      g.DrawImage (entireMap, new Rectangle (0, 0, saveArea.Width, saveArea.Height), saveArea);
    }

    4) do as you please on the original bitmap
    ...

    5) restore the saved bitmap:

    using (Graphics g = Graphics.FromImage (entireMap)) {
      g.DrawImage (savedBitmap, saveArea, new Rectangle (0, 0, saveArea.Width, saveArea.Height));
    }

    Hope I got that right
    --mc
    • Marked As Answer byBadButBit Sunday, November 01, 2009 3:28 PM
    • Unmarked As Answer byBadButBit Saturday, November 07, 2009 5:07 PM
    •  
  • Sunday, November 01, 2009 3:29 PMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    new voodoo to use!

    this will greatly improve my graphics! 

    thanks, BadButBit
    my code is perfect until i don't find a bug
  • Saturday, November 07, 2009 5:16 PMBadButBit Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     Has Code
    hi,
    i've been working on other parts of my project and have finally gotten around to wanting to use your last posting's suggested code and now discover that it doesn't work as posted.

    first it takes an image then chops it up into square and draws these squares whereever they belong on the output picturebox.

    this is the code where I'm chopping the main image :


      void newPuzzle(Image img)
            {
                Bitmap bmpPic = new Bitmap(img);
    
                intWidth = (int)Math.Floor((double)bmpPic.Width / (double)intSquareSize);
                intHeight = (int)Math.Floor((double)bmpPic.Height / (double)intSquareSize);
                bmp = new Bitmap[intWidth, intHeight];
                udrImageIndices = new udtCartesian[intWidth, intHeight];
    
                for (int intX = 0; intX < intWidth; intX++)
                    for (int intY = 0; intY < intHeight; intY++)
                    {
                        Rectangle recSaveArea = new Rectangle(intX * intSquareSize, intY * intSquareSize, intSquareSize, intSquareSize);
                        bmp[intX, intY] = new Bitmap(intSquareSize, intSquareSize);
                        if (!(intX == intWidth - 1 && intY == intHeight - 1)) // leave bottom right blank
                        {
                            if (false)
                            { // this works but is much too slow
                                for (int intPX = 0; intPX < intSquareSize; intPX++)
                                    for (int intPY = 0; intPY < intSquareSize; intPY++)
                                        bmp[intX, intY].SetPixel(intPX, intPY, bmpPic.GetPixel(recSaveArea.Left + intPX, recSaveArea.Top + intPY));
                            }
                            else
                            {
                                Rectangle saveArea = new Rectangle(intX * intSquareSize, intY * intSquareSize, intSquareSize, intSquareSize);
                                bmp[intX, intY] = new Bitmap(saveArea.Width, saveArea.Height);
                                using (Graphics g = Graphics.FromImage(bmp[intX, intY]))
                                { g.DrawImage(bmpPic, new Rectangle(0, 0, saveArea.Width, saveArea.Height), saveArea); } // <- invalid parameters
                            }
                        }
                        udrImageIndices[intX, intY] = getCartesian(intX, intY);
                    }
               
                Controls.Add(pic);
    
                pic.Click += new EventHandler(pic_Click);
                pic.MouseMove += new MouseEventHandler(pic_MouseMove);
                pic.Visible = true;
                drawPuzzle();
    
                System.Windows.Forms.Timer tmrScramble = new Timer();
                tmrScramble.Interval = 10000;
                tmrScramble.Tick += new EventHandler(tmrScramble_Tick);
                tmrScramble.Enabled = true;
            }
    


    you'll notice the

    if (false)
    
    which i set to true just to use the much slower and impractical bit-setting alternative to test the output and finish the rest of this little project as a preliminary to get back to my other war-game.  and that bit-setting routine works though much too slow to be of any real practical use.  when i try the parameters <image, int, int, int, int> for the .drawimage() function the entire original image is fitted into the 'rectangle' which is drawn out by the four integers onto the copy image.  since what i want is a fraction of the original described by this rectangle copied onto an equal sized rectangle at (0,0) of the copy image, this seems like a dead end.

    can someone clarify?

    BadButBit
    my code is perfect until i don't find a bug
  • Tuesday, November 10, 2009 12:49 AMMario Cossi Users MedalsUsers MedalsUsers MedalsUsers MedalsUsers Medals
     
    Hi, sorry for the delay... I didn't notice your post.

    Yes, my sample doesn't compile: I missed a parameter. The actual call should be:

    g.DrawImage (bmpPic, new Rectangle (0, 0, saveArea.Width, saveArea.Height), saveArea, GraphicsUnit.Pixel);

    Since the size of the destination is the same as the source, you can also use:

    g.DrawImage (bmpPic, 0, 0, saveArea, GraphicsUnit.Pixel);

    the size of the destination rectangle will be the same of the source.

    Sorry for the mistake
    --m