none
Set bitmap width with BitmapData RRS feed

  • Question

  • Hello, 

    I know how to draw a bitmap onto a new image, but this is not what I am asking.

    I am asking if there is a way to change a bitmap's width, without creating a new bitmap object, without disposing the bitmap.

    According to documentation, BitmapData.Width, and BitmapData.Height should allow for the width and height changing of a bitmap.

    Unfortunately, I am unable to get this to work correctly. 

    Here is my failing code:

        Sub resizeImage(ByRef bitmap As Bitmap, newWidth As Integer, newHeight As Integer)
            Dim bits As BitmapData = bitmap.LockBits(New Rectangle(New Point(0, 0), bitmap.Size), ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
            bits.Width = newWidth
            bits.Height = newHeight
            bitmap.UnlockBits(bits)
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim bm As New Bitmap(1, 1, Imaging.PixelFormat.Format32bppArgb)
            resizeImage(bm, 200, 200)
            Using g As Graphics = Graphics.FromImage(bm)
                g.FillRectangle(Brushes.Red, New Rectangle(0, 0, 200, 200))
            End Using
            pbTileSetA.Image = bm
        End Sub

    Thanks for any help


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 1:48 AM
    Moderator

All replies

  • Hi Paul,

    I haven't tried it to see how it fails or test my guess, but you may just need to write some new pixel data to the BitmapData - changing the width and height may not repopulate the image byte array.

    I'm curious as to why you'd want to do this versus just creating a new bitmap and drawing the old one to it?


    Reed Kimble - "When you do things right, people won't be sure you've done anything at all"

    Tuesday, January 16, 2018 2:08 AM
    Moderator
  • Hi Paul,

    I haven't tried it to see how it fails or test my guess, but you may just need to write some new pixel data to the BitmapData - changing the width and height may not repopulate the image byte array.

    I'm curious as to why you'd want to do this versus just creating a new bitmap and drawing the old one to it?

    Hmm, gonna try that next I guess....

    As far as why... Just trying to keep a working "canvas", and avoid the creation of and disposing of several thousands of bitmaps and avoid creating memory leaks/excess GDI objects.


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 2:11 AM
    Moderator
  • Hi Paul,

    I haven't tried it to see how it fails or test my guess, but you may just need to write some new pixel data to the BitmapData - changing the width and height may not repopulate the image byte array.

    Just tried that, and it crashes the program for my best guess is corrupting the heap.

    I imagine this is one of those things where Height and Width of bitmapdata were supposed to be readonly, but instead somehow have been overlooked for years.

    Maybe I tried to populate the array incorrectly... Failing attempt....:

        Sub resizeImage(ByRef bitmap As Bitmap, newWidth As Integer, newHeight As Integer)
            Dim bits As BitmapData = bitmap.LockBits(New Rectangle(New Point(0, 0), bitmap.Size),
                                                     ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
            bits.Width = 100
            bits.Height = 100
            Dim InDir As IntPtr = bits.Scan0
            Dim bytes As Integer = (bits.Stride) * bits.Height
            Dim argb(bytes - 1) As Byte
            System.Runtime.InteropServices.Marshal.Copy(InDir, argb, 0, bytes)
            For counter As Integer = 0 To argb.Length - 1
                argb(counter) = 255
            Next
            System.Runtime.InteropServices.Marshal.Copy(argb, 0, InDir, bytes)
            bitmap.UnlockBits(bits)
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim bm As New Bitmap(1, 1, Imaging.PixelFormat.Format32bppArgb)
            resizeImage(bm, 200, 200)
            Using g As Graphics = Graphics.FromImage(bm)
                g.FillRectangle(Brushes.Red, New Rectangle(0, 0, 200, 200))
            End Using
            pbTileSetA.Image = bm
        End Sub

     

    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 2:29 AM
    Moderator
  • Even if I manually allocate memory... It still doesn't work.. Doesn't crash, but the bitmap doesn't change width...

        Sub resizeImage(ByRef bitmap As Bitmap, newWidth As Integer, newHeight As Integer)
            Dim bits As BitmapData = bitmap.LockBits(New Rectangle(New Point(0, 0), bitmap.Size),
                                                     ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
            bits.Width = 100
            bits.Height = 100
            Dim bytes As Integer = 40000
            Dim newPTR As IntPtr = Marshal.AllocHGlobal(bytes)
            bits.Scan0 = newPTR
            Dim argb(bytes - 1) As Byte
            System.Runtime.InteropServices.Marshal.Copy(newPTR, argb, 0, bytes)
            For counter As Integer = 0 To argb.Length - 1
                argb(counter) = 255
            Next
            System.Runtime.InteropServices.Marshal.Copy(argb, 0, newPTR, bytes)
            bitmap.UnlockBits(bits)
        End Sub
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim bm As New Bitmap(1, 1, Imaging.PixelFormat.Format32bppArgb)
            resizeImage(bm, 200, 200)
            Using g As Graphics = Graphics.FromImage(bm)
                g.FillRectangle(Brushes.Red, New Rectangle(0, 0, 200, 200))
            End Using
            pbTileSetA.Image = bm
        End Sub


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 2:37 AM
    Moderator
  • Hi,

    just a guess, you need to set the stride of the bitmapdata to change its width (in 32bpp bitmaps the stride always width * 4)

    Regards,

      Thorsten

    Hmm, I tried that too... Maybe I messed something up?

        Sub resizeImage(ByRef bitmap As Bitmap, newWidth As Integer, newHeight As Integer)
            Dim BMD As BitmapData = bitmap.LockBits(New Rectangle(New Point(0, 0), bitmap.Size),
                                                     ImageLockMode.ReadWrite, Imaging.PixelFormat.Format32bppArgb)
    
            Dim Length As Integer = (newWidth * 4) * newHeight
            Dim newBMD As New BitmapData()
            newBMD.Height = newWidth
            newBMD.Width = newHeight
            newBMD.Stride = newBMD.Width * 4
            newBMD.PixelFormat = PixelFormat.Format32bppArgb
            newBMD.Scan0 = Marshal.AllocHGlobal(Length)
    
            Dim argb(Length - 1) As Byte
    
            System.Runtime.InteropServices.Marshal.Copy(newBMD.Scan0, argb, 0, Length)
            For i As Integer = 0 To Length - 1
                argb(i) = 255
            Next
    
            BMD.Height = newBMD.Height
            BMD.Width = newBMD.Width
            BMD.Stride = newBMD.Stride
            BMD.PixelFormat = newBMD.PixelFormat
            BMD.Scan0 = newBMD.Scan0
    
    
            System.Runtime.InteropServices.Marshal.Copy(argb, 0, BMD.Scan0, Length)
            bitmap.UnlockBits(BMD)
        End Sub


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 4:29 AM
    Moderator
  • Hi,

    something to start with, is not what you want yet...

    I think, if you want to change the physical dimensions of the bitmap, you probably need to create a new Bitmap object... [the native parts of the current bitmap object maybe cant be changed, else the width property of the object wouldnt be restricted to "get"]

    Option Strict On
    
    Imports System.Drawing.Imaging
    Imports System.Runtime.InteropServices
    
    Public Class Form1
        Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            Dim b As New Bitmap(Me.PictureBox1.ClientSize.Width, Me.PictureBox1.ClientSize.Height)
    
            Dim bd As New BitmapData()
            bd.Stride = b.Width * 2
            bd.Width = b.Width \ 2
            bd.Height = b.Height \ 2
            bd.PixelFormat = PixelFormat.Format32bppArgb
    
            Dim bytes As Integer = bd.Stride * bd.Height
            Dim argb(bytes - 1) As Byte
    
            Dim Handle As GCHandle = GCHandle.Alloc(argb, GCHandleType.Pinned)
            bd.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(argb, 0)
    
            Dim bits As BitmapData = b.LockBits(New Rectangle(New Point(0, 0), New Size(bd.Width, bd.Height)),
                                                ImageLockMode.UserInputBuffer Or ImageLockMode.ReadWrite, bd.PixelFormat, bd)
    
            For counter As Integer = 0 To argb.Length - 1
                argb(counter) = 127
            Next
    
            b.UnlockBits(bits)
            Handle.Free()
    
            MsgBox(b.Size.ToString)
    
            Me.PictureBox1.Image = b
        End Sub
    End Class

    see:

    https://stackoverflow.com/questions/6782489/create-bitmap-from-a-byte-array-of-pixel-data

    [the reply that begins with "concerning your question4"]

    Regards,

      Thorsten

    Tuesday, January 16, 2018 5:09 AM
  • Paul,

    I guess it is likewise changing the size of the USA by changing that in Wikipedia.


    Success Cor

    Tuesday, January 16, 2018 7:31 AM
  • I did some tests and it does not seem to make a lot of difference Don Quixote.

    In button 1 a new bmp is made and button2 I use the declared bmp in memory that is already the correct size.

    Then, removing the resize routine in button1 and using in line code instead gives a bigger gain in speed than eliminating the temp bmp.

    In button 1 I comment out the sub routine and use in line code instead for the 3rd test which seems the fastest of all three. Try that.

    Also removing any graphics statements like transform will give good gains etc (not shown).

    Check it for errors I may have made.

    :)

    Public Class Form4
        Private orgBmp As New Bitmap("c:\bitmaps\rusty.jpg")
        Private constBmp As New Bitmap(150, 150)
    
        Dim sw As New Stopwatch
    
        Private Sub Form4_Load(sender As Object, e As EventArgs) Handles MyBase.Load
            PictureBox1.BackgroundImageLayout = ImageLayout.None
            PictureBox2.BackgroundImageLayout = ImageLayout.None
            PictureBox1.ClientSize = New Size(orgBmp.Width, orgBmp.Height)
            PictureBox2.ClientSize = New Size(orgBmp.Width, orgBmp.Height)
            Dim border As Integer = 20
            PictureBox1.Location = New Point(border, border)
            PictureBox2.Location = New Point(PictureBox1.Left, PictureBox1.Bottom + border)
            Button1.Location = New Point(100, PictureBox2.Bottom + border)
            Button2.Location = New Point(200, PictureBox2.Bottom + border)
    
            ClientSize = New Size(PictureBox1.Width + (2 * border), PictureBox2.Bottom + (3 * border))
            BackColor = Color.Black
            PictureBox1.BackColor = Color.Red
            PictureBox2.BackColor = Color.Red
    
            PictureBox1.BackgroundImage = orgBmp
        End Sub
    
        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            sw.Reset()
            sw.Start()
            Dim w As Integer = 150
            Dim h As Integer = 150
            For j As Integer = 1 To 10
                For i As Integer = 1 To 1000
                    'PictureBox2.Image = ResizeBmp1(orgBmp, 150, 150)
                    Using Bmp2 As Bitmap = New Bitmap(w, h),
                        g As Graphics = Graphics.FromImage(Bmp2)
    
                        g.DrawImage(orgBmp, 0, 0, w, h)
    
                        PictureBox2.Image = CType(Bmp2.Clone, Bitmap)
    
                    End Using
                Next
            Next
            sw.Stop()
            'MsgBox("New Bmp: " & sw.ElapsedMilliseconds.ToString)
            MsgBox("New bmp no sub: " & sw.ElapsedMilliseconds.ToString)
    
        End Sub
    
        Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
            sw.Reset()
            sw.Start()
            For j As Integer = 1 To 10
                For i As Integer = 1 To 1000
                    PictureBox2.Image = ResizeBmp2(orgBmp)
                Next
            Next
            sw.Stop()
            MsgBox("No New Bmp: " & sw.ElapsedMilliseconds.ToString)
        End Sub
    
        Private Function ResizeBmp1(ByRef bmp1 As Bitmap, w As Integer, h As Integer) As Bitmap
            Using Bmp2 As Bitmap = New Bitmap(w, h),
                    g As Graphics = Graphics.FromImage(Bmp2)
    
                g.DrawImage(bmp1, 0, 0, w, h)
    
                Return CType(Bmp2.Clone, Bitmap)
    
            End Using
        End Function
    
        Private Function ResizeBmp2(ByRef bmp1 As Bitmap) As Bitmap
    
            Using g As Graphics = Graphics.FromImage(constBmp)
    
                g.DrawImage(bmp1, 0, 0, constBmp.Width, constBmp.Height)
    
                Return CType(constBmp.Clone, Bitmap)
    
            End Using
        End Function
    End Class

    Tuesday, January 16, 2018 12:13 PM
  • Paul,

    I guess it is likewise changing the size of the USA by changing that in Wikipedia.


    Success Cor

    Maybe I can invent a way to do it by reading the source for a bitmap object.

    http://referencesource.microsoft.com/#System.Drawing/commonui/System/Drawing/Bitmap.cs


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 1:18 PM
    Moderator
  • I did some tests and it does not seem to make a lot of difference Don Quixote.


    For this, render time is not even the concern...

    The challenge is to resize a bitmap, without creating a new bitmap, and without disposing the bitmap that is being resized.

    The challenge is to resize the actual bitmap in question.


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 1:31 PM
    Moderator
  • Paul,

    What do you think the benefit will be from your goal?


    Success Cor

    Tuesday, January 16, 2018 3:49 PM

  • The challenge is to resize a bitmap, without creating a new bitmap, and without disposing the bitmap that is being resized.

    The challenge is to resize the actual bitmap in question.


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Hi Paul,

    but how to act with the old data. Its unmanaged data and so its not self-disposing. Disposing the old and creating a new is the easiest and cleanest way to do. A gdi+ bitmap is just the Data array and some parts of the Header info like that BitmapData object... So changing the physical size is to create a new one since you cannot simply set a new width to a 2d array, the overall amount of data (overall length of the array) must notr change then.

    Regards,

      Thorsten

    Tuesday, January 16, 2018 9:06 PM

  • Hi Paul,

    but how to act with the old data. Its unmanaged data and so its not self-disposing. Disposing the old and creating a new is the easiest and cleanest way to do. A gdi+ bitmap is just the Data array and some parts of the Header info like that BitmapData object... So changing the physical size is to create a new one since you cannot simply set a new width to a 2d array, the overall amount of data (overall length of the array) must notr change then.

    Regards,

      Thorsten

    It doesn't really matter because even if I create a new BitmapData object and allocate memory on the heap or even with GC.alloc, and change Scan0 to a pointer of that allocation, it does not change the location or content of bitmap's data. Essentially, changing the properties on the bitmapdata object will not affect the bitmap in any way, Alas, bitmapdata only contains information to use for marshaling, and modifying the bitmapdata object will not directly affect the bitmap itself. I was hoping I could find an underlying way of changing the pointer to the data by reviewing the source code for the bitmap object. Alas, probably not possible, and I am going to abandon this effort for now.


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    Tuesday, January 16, 2018 10:00 PM
    Moderator
  • Hi,

    something to start with, is not what you want yet...

    I think, if you want to change the physical dimensions of the bitmap, you probably need to create a new Bitmap object... [the native parts of the current bitmap object maybe cant be changed, else the width property of the object wouldnt be restricted to "get"]

    Hello Thorsten,

    This is neat... And close, but unfortunately this doesn't work. This is an approach I was attempting earlier, but I was failing due to calculating the stride incorrectly.

    I have adjusted and tried some formulas, and discovered(to the best of my knowledge), that this will only work within the existing allocated space... Or something else that I can't quite explain(because of the allocating of a separate block of memory for the subselection of the image). Either way, the height and width of the bitmap itself remain static both on the properties of the bitmap object, and in what is actually displayed in the picturebox, although this will isolate and allow the manipulation of the specified rectangle of pixels within the image.

    Try this example:

        Sub resize2(ByRef bitmap As Bitmap, newWidth As Integer, newHeight As Integer)
            Dim newBMD As New BitmapData()
            Dim bitsPerPixel As Integer = 32
            Dim stride As Integer = ((newWidth * bitsPerPixel) + 32) \ 8
            Dim bytes As Integer = stride * newHeight
            Dim argb(bytes - 1) As Byte
            Dim Handle As GCHandle = GCHandle.Alloc(argb, GCHandleType.Pinned)
            newBMD.Stride = stride
            newBMD.Width = newWidth
            newBMD.Height = newHeight
            newBMD.PixelFormat = PixelFormat.Format32bppArgb
            newBMD.Scan0 = Marshal.UnsafeAddrOfPinnedArrayElement(argb, 0)
            Dim lockRect As New Rectangle(New Point(0, 0), New Size(newWidth, newHeight))
            Dim lockMode As ImageLockMode = ImageLockMode.WriteOnly Or ImageLockMode.UserInputBuffer
            Dim BMD As BitmapData = bitmap.LockBits(lockRect, lockMode, newBMD.PixelFormat, newBMD)
            For i As Integer = 0 To argb.Length - 1 Step 4
                argb(i) = 0 'blue
                argb(i + 1) = 0 'green
                argb(i + 2) = 255 'red
                argb(i + 3) = 255 'alpha
            Next
            bitmap.UnlockBits(BMD)
            Handle.Free()
        End Sub

    You will find that if you resize to a smaller image, it will not crash, but the image will not change its size, but rather only change the constraints within the selected bitmap rectangle(the custom BMD is a subselection of the Bitmaps "Entire" bitmap data.) This does not resize the bitmap.

    If you attempt to resize to a size larger than the original bitmap, the program crashes, my guess is due to some type of memory access violation.

    Example usage:

        Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
            Dim newWidth As Integer = 99
            Dim newHeight As Integer = 99
            Dim b As New Bitmap(100, 100)
            Using g As Graphics = Graphics.FromImage(b)
                g.FillRectangle(Brushes.Blue, New Rectangle(0, 0, 100, 100))
            End Using
            pbTileSetA.Image = b
            MsgBox("Look at the image in the picturebox...")
            resize2(b, 50, 50)
            pbTileSetA.Refresh()
            MsgBox("Now Test 2, we resize larger...")
            resize2(b, 150, 150)
            pbTileSetA.Refresh()
        End Sub


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.



    Wednesday, January 17, 2018 2:51 AM
    Moderator


  • It doesn't really matter because even if I create a new BitmapData object and allocate memory on the heap or even with GC.alloc, and change Scan0 to a pointer of that allocation, it does not change the location or content of bitmap's data.


    Hire Me For This Job!
    Don't forget to vote for Helpful Posts and Mark Answers!
    *This post does not reflect the opinion of Microsoft, or its employees.

    The information of the beginning and length of any objects data must be stored somewhere in a description of that object. And I really dont think that you could overwrite such crucial numbers in memory (for security reasons).

    From my experiance its better to create a class that holds a display bitmap and when receiving a change notification for that, it creates a new Bitmap to display and deletes the old (and maybe changes some lists that store information of whats currently displayed in the Canvas...)

    Regards,

      Thorsten

    Wednesday, January 17, 2018 4:02 AM