The TextureBrush class lets you draw a shape using an image as a texture, giving you some control over the way the texture is tiled. I encountered a forum post where a user was trying to use TextureBrush to draw sprites to the screen, but he was upset because as he moved the rectangle around it wasn’t moving the texture; the result was similar to having a tiled background covered up and viewed through a small window, then moving the window around.
I looked at the documentation, and saw constructors that let you specify a bounding rectangle. I misinterpreted what the bounding rectangle did, and thought it specified where the top-left corner of the tiling should start. The odd thing was introducing this rectangle led to a generic GDI exception with the message "Out of memory." I found this curious. At this point I realized that for the problem at hand, there was no reason to use a TextureBrush as opposed to Graphics.DrawImage, so I suggested this course of action. Of course, I was curious why there was an out of memory exception, so I dug deeper.
Google was no real help; there were basically two forum threads about the problem, and none had a resolution (one suggested a possible accidental recursive paint handler, but that’s ridiculous since the second message wouldn’t be processed until the first completes and it had nothing to do with this case.) I was about to turn to the MSDN forums when I dug about in the Connect area for any bug reports concerning this. I found my answer, but it’s somewhat unsatisfactory.
Consider the example image below; it’s a 50×40 image lifted from the VS 2008 image library that represents 2 16×16 images:
We can use a TextureBrush to draw rectangles using this image; by default the result is a silly tiling of back/forward buttons, and if we move the rectangle around it will look as if we’re sliding a window over a fixed pattern, as described above. If we specify a bounding rectangle, only the part of the image within the bounding rectangle will be used as the tile. For example, the following paint handler would tile only the left-facing arrow from the above image:
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics Dim boundRect As Rectangle = New Rectangle(7, 12, 15, 15) Dim brush As New TextureBrush(drawImage, boundRect) g.FillRectangle(brush, Me.ClientRectangle) End Sub
If the bounding rectangle that is specified is outside the bounds of the image that is to be tiled, you get the "Out of Memory" exception. I don’t understand why there’s no error checking done; it would be trivial to check whether dstRect is outside of the bounds of image, but for whatever reason this was left out of the method and instead you get a cryptic error message that doesn’t really tell you what is wrong.
I hope this ends up on Google and anyone who has the problem in the future finds it helpful.
To demonstrate using TextureBrush in the manner the user wanted, the application below animates a left or right arrow back and forth across the form. I don’t believe this is the best way to do this, and I’m almost certain that one of the Graphics.DrawImage overloads would be more appropriate, but there could be a use for this I have not forseen:
Public Class Form1 Dim drawImage As Image Dim boundRect As Rectangle Dim leftPoint As New Point(7, 12) Dim rightPoint As New Point(28, 12) Dim tileSize As New Size(15, 15) Dim viewRect As New Rectangle(New Point(0, 0), tileSize) Dim goingLeft As Boolean = False Public Sub New() ' This call is required by the Windows Form Designer. InitializeComponent() ' Add any initialization after the InitializeComponent() call. drawImage = Image.FromFile("Back_Forward.png") Dim timer As New Timer() timer.Interval = 100 AddHandler timer.Tick, AddressOf TimerOnTick timer.Start() boundRect = New Rectangle(rightPoint, tileSize) End Sub Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs) Dim g As Graphics = e.Graphics Dim brush As New TextureBrush(drawImage, boundRect) brush.TranslateTransform(viewRect.X, viewRect.Y) g.FillRectangle(brush, viewRect) End Sub Private Sub TimerOnTick(ByVal sender As Object, ByVal e As EventArgs) Dim increment As Integer If goingLeft Then increment = -10 Else increment = 10 End If viewRect.X += increment If viewRect.X <= 0 Then goingLeft = False boundRect = New Rectangle(rightPoint, tileSize) ElseIf viewRect.X + viewRect.Width >= Me.ClientRectangle.Width Then goingLeft = True boundRect = New Rectangle(leftPoint, tileSize) End If Me.Invalidate() End Sub End Class
Thanks for that awesome explanation, you probably saved me another fruitless hour of fighting with the FillRectangle.
I made the same mistake and assumed the image would start at the top left of the object to be filled and it took me longer than I would care to admit to figure what was going on.
I am trying to scale a 1,30 image directly to the right over different lengths and for some reason when I use the DrawImage it scales it but fads the image over the course of the width. Very frustrating..So I turned to the FillRectangle which seemed to work apart from the alignment problem.
So thanks for the solution. If you know why the DrawImage fads out I would be keen to hear.
Cheers,
Ross
http://www.teameffect.com
I know this is an old post, but for anyone seeing this the reason to use the texture brush instead of DrawImage is performance. DrawImage is incredibly slow and should be avoided whereever possible. I’ve writing a basic report system using GDI+ and changing from DrawImage to texturebrush changed the framerate from about 5fps (with several large images on the screen) to 60.
hey bro thank you very much
I am working with this problem from long time but not able to understand the problem and also the error message does not provide any help
So finally from this article i got the useful information
I was having a similar issue, but was avoiding DrawImage for it’s slower performance.
I say ‘similar’ because the rectangle I was specifying was within the bounds of the image, so that could not have been causing the error.
However, I noticed the error only occurred when the source image has an unusual resolution.
myBitmap.VerticalResolution = 95.9865952
instead of
myBitmap.VerticalResolution = 96.0