0

I have a list of restaurant names, I need to draw these strings to a Bitmap.
I create the Bitmap, draw the text but then if I do not save the Bitmap and then load the saved file into a Bitmap before adding it to the list, the Bitmap is invalid.
Here is my code, with many names redacted for brevity, I am hoping someone can explain why and how I can avoid saving to disk:

Option Strict On
Imports System.IO

Public Class Form1
    Private R As New Random
    Private Places As New List(Of String)
    Private Images As New List(Of Bitmap)
    Private TheFont As Font = New Font("Engravers MT", 18)

    Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
        PictureBox1.Visible = False
        Using g As Graphics = Me.CreateGraphics

            For Each S As String In Places ' List of Restaurant Names
                Dim SF As SizeF = g.MeasureString(S, TheFont)
                TextBox1.AppendText(S & " = " & SF.Width & ", " & SF.Height & vbNewLine)
                PictureBox1.Size = New Size(CInt(SF.Width), CInt(SF.Height))

                Using BM As Bitmap = New Bitmap(PictureBox1.Width, PictureBox1.Height)
                    Using gg As Graphics = Graphics.FromImage(BM)
                        gg.Clear(Color.White)
                        gg.DrawString(S, TheFont, Brushes.Black, 0, 0)
                        gg.Flush()
                    End Using
                    'Code that WORKS
                    BM.Save("D:\AAAAAA\" & S & ".jpg", Imaging.ImageFormat.Jpeg) '*************************
                    Images.Add(CType(Image.FromFile("D:\AAAAAA\" & S & ".jpg"), Bitmap)) '********************************
                    ''The code above DOES WORK

                    'The code below DOES NOT WORK, using only one of the two at a time
                    'Images.Add(CType(BM, Bitmap)) '************************************
                    'Images.Add(BM) '************************************
                    'Code that DOES NOT WORK
                End Using
            Next
        End Using
        Stop ' for debugging
    End Sub

    Private Sub Button3_Click(sender As Object, e As EventArgs) Handles Button3.Click
        PictureBox1.Visible = True
        Dim Index As Integer = R.Next(0, Images.Count)
        Dim B As Bitmap = Images(Index)            ' was .Clone
        PictureBox1.Width = B.Width
        PictureBox1.Height = B.Height
        PictureBox1.Image = B
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Places.Add("Arby's")
        Places.Add("Baja Fresh Mexican Grill")
        Places.Add("Black Bear Diner")
        Places.Add("Burger King")
        Places.Add("Carl's Jr.")
        Places.Add("Chick-fil-A")
    End Sub
End Class
Jimi
  • 29,621
  • 8
  • 43
  • 61
zomalaja
  • 9
  • 3
  • Does this answer your question? [How to stretch a Bitmap to fill a PictureBox](https://stackoverflow.com/questions/7920058/how-to-stretch-a-bitmap-to-fill-a-picturebox) – Peter Duniho Feb 22 '21 at 02:54
  • _"I don't know how to "mark it as answered"_ -- your question is a duplicate of an existing one, the answer to which also explains that you should not dispose `Bitmap` objects (i.e. don't use a `using` statement) if they are to be used outside the method that produces them. You can "mark it answered" simply by clicking the button on the banner above that says that the duplicate answers your question. – Peter Duniho Feb 22 '21 at 02:57

1 Answers1

1

The problem you experience is caused by the Using block implicitly calling Dispose() on the Bitmap objects you create.
When you create a Bitmap object and add it to a List(Of Bitmap), you actually add a a reference to the Bitmap object. The list doesn't contain the objects, just the references.

Since you need to preserve those Bitmaps, don't dispose of them. Just remove the Using statement when you declare a new Bitmap.

I've changed some of the names of the Fields / Variables, to follow base naming conventions.

Also, added [Graphics].TextRenderingHint = TextRenderingHint.AntiAliasGridFit to perform an anti-aliased rendering of the text. See what other effect you have using other TextRenderingHint settings as AntiAlias or ClearTypeGridFit (<= when drawing on a Control's surface) and the [Graphics].TextContrast property.

Private textImages As New List(Of Bitmap)
Private bitmapFont As Font = New Font("Engravers MT", 18)
' [...]


' [... loop the collection of strings]
Using g As Graphics = CreateGraphics()
    Dim textSize As Size = Size.Round(g.MeasureString(s, bitmapFont))
    Dim bmp As Bitmap = New Bitmap(textSize.Width, textSize.Height)

    Using bg As Graphics = Graphics.FromImage(bmp)
        bg.Clear(Color.White)
        bg.TextRenderingHint = TextRenderingHint.AntiAliasGridFit
        bg.DrawString(s, bitmapFont, Brushes.Black, 0, 0)
        textImages.Add(bmp)
    End Using
End Using

It's however important that you dispose of the Bitmaps (and any other object that references unmanaged resources, as the Font object) when you don't need these objects anymore.
For example, since you're using a Form, you can dispose of these objects when the Form closes:

Private Sub Form1_FormClosed(sender As Object, e As FormClosedEventArgs) Handles MyBase.FormClosed
    bitmapFont?.Dispose()
    If textImages IsNot Nothing Then
        For i As Integer = textImages.Count - 1 To 0 Step -1
            textImages(i)?.Dispose()
        Next
    End If
End Sub

In case you want to remove the automatic padding added to the rendered Text, see here:
Remove top and bottom padding from Text drawn on an Image

Other notes that may be interesting here:
Properly draw text using Graphics Path

Jimi
  • 29,621
  • 8
  • 43
  • 61