Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

VBDwgImageExtractor

0.00/5 (No votes)
22 Nov 2007 2  
An article on trying to read the BMP preview Image data from Autocad (R13C3 and later) DWG drawing.
Screenshot - VBDwgImageExtractor.jpg

Introduction

Some time ago, a friend and coworker of mine needed to get the preview image stored in a DWG Autocad Drawing from a Visual Basic application to automate some tasks in AutoCAD. While searching the internet, we found the OpenDWG specification (rtf) from the Open Design Alliance Website, and were able to do it. We try here to get the BMP drawing preview image into a pictureBox using Visual Basic .NET.

Create the Visual Basic Application Project:

  1. File -> New -> New project
  2. Create a New VBDwgImageExtractor Visual Basic Windows Application
  3. Change the Form1 Text field to VBDwgImageExtractor
  4. From the Toolbox, drag-n-drop a PictureBox into the form
  5. Drag-n-drop a Button and an openFileDialog into the form
  6. Set the openFileDialog1 Filter property to Drawing files (*.dwg)|*.dwg|All files (*.*)|*.*, and the DefaultExt to dwg
  7. Now double click the Button1 button, to create and edit the Button1_Click event

Change the Button1_Click method with code to open and show the BMP preview as follows:

Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click

        If (OpenFileDialog1.ShowDialog() = DialogResult.OK) Then
            Dim path As String = OpenFileDialog1.FileName
            ' Create the reader for data.

            Dim fs As FileStream = _
                New FileStream(path, FileMode.Open, FileAccess.Read, 
                    FileShare.ReadWrite)
            Dim r As BinaryReader = New BinaryReader(fs)

            ' Get the image position in the DWG file

            r.BaseStream.Seek(&HD, SeekOrigin.Begin)
            Dim imgPos As Int32 = r.ReadInt32()
            r.BaseStream.Seek(imgPos, SeekOrigin.Begin)

            ' Image sentinel to check if the image data 

            ' is not corrupted

            Dim imgBSentinel() As Byte = _
                {&H1F, &H25, &H6D, &H7, &HD4, &H36, &H28, _
                &H28, &H9D, &H57, &HCA, &H3F, &H9D, &H44, &H10, &H2B}

            ' Read Image sentinel

            Dim imgCSentinel(16) As Byte
            imgCSentinel = r.ReadBytes(16)

            ' if image sentinel is correct

            If (imgBSentinel.ToString() = imgCSentinel.ToString()) Then
                ' Get image size

                Dim imgSize As UInt32 = r.ReadUInt32()

                ' Get number of images present

                Dim imgPresent As Byte = r.ReadByte()

                ' header

                Dim imgHeaderStart As Int32 = 0
                Dim imgHeaderSize As Int32 = 0

                ' bmp data

                Dim imgBmpStart As Int32 = 0
                Dim imgBmpSize As Int32 = 0
                Dim bmpDataPresent As Boolean = False

                ' wmf data

                Dim imgWmfStart As Int32
                Dim imgWmfSize As Int32
                Dim wmfDataPresent As Boolean = False

                ' get each image present

                For I As Integer = 1 To imgPresent

                    ' Get image type

                    Dim imgCode As Byte = r.ReadByte()
                    Select Case imgCode

                        Case 1
                            ' Header data

                            imgHeaderStart = r.ReadInt32()
                            imgHeaderSize = r.ReadInt32()
                        Case 2
                            ' bmp data

                            imgBmpStart = r.ReadInt32()
                            imgBmpSize = r.ReadInt32()
                            bmpDataPresent = True
                        Case 3
                            ' wmf data

                            imgWmfStart = r.ReadInt32()
                            imgWmfSize = r.ReadInt32()
                            wmfDataPresent = True
                    End Select
                Next

                If (bmpDataPresent) Then

                    r.BaseStream.Seek(imgBmpStart, SeekOrigin.Begin)

                    Dim tempPixelData(imgBmpSize + 14) As Byte

                    ' indicate it is a bit map

                    tempPixelData(0) = &H42
                    tempPixelData(1) = &H4D

                    ' offBits

                    tempPixelData(10) = &H36
                    tempPixelData(11) = &H4

                    Dim tempBuffData(imgBmpSize) As Byte

                    tempBuffData = r.ReadBytes(imgBmpSize)

                    tempBuffData.CopyTo(tempPixelData, 14)

                    Dim memStream As MemoryStream = _
                            New MemoryStream(tempPixelData)

                    Dim bmp As Bitmap = New Bitmap(memStream)

                    PictureBox1.Image = bmp

                End If

                If (wmfDataPresent) Then
                    ' read imgWmfSize wmf data

                End If

                Dim imgESentinel() As Byte = _
                    {&HE0, &HDA, &H92, &HF8, &H2B, &HC9, &HD7, _
                    &HD7, &H62, &HA8, &H35, &HC0, &H62, &HBB, &HEF, &HD4}

                imgCSentinel = r.ReadBytes(16)

                ' if image sentinel is correct

                If (imgESentinel.ToString() = imgCSentinel.ToString()) Then

                    ' Image data is not corrupted

                End If

            End If

            fs.Close()

        End If
    End Sub

Add the following Imports to the top of Form1.vb code:

    Imports System.Drawing
    Imports System.IO

Build the Solution (F7), and run the application (F5).

Click Button1 and browse for the DWG file, and open it.

Explanation

Autocad DWG Structure

As the Open Design Alliance discovered, at &HD there is a seeker, that is a 4 byte long absolute address of the beginning of the image data. So we open a file stream and create a binary reader from it to access the DWG file. Then we seek the address &HD, and read the Int32 seek position of the first sentinel for the preview Image data.

A sentinel is just a constant 16 long byte array used to check the file for consistency, or as a marker. We have one for the beginning and another for the end of the image.

So, again we seek the image position imgPos and read the 16 bytes of the first sentinel and compare it.

Then after the sentinel, there comes a header Int32 value with the overall size of the image area. This is followed by a Byte which is a counter of the number of image data formats present (1-3) imgPresent.

We repeat imgPresent times, to get first the image header start position in the file, and header size; second, the BMP data start position and size; and third, the WMF data position and size, if present.

If we find a BMP data present, we seek the start position of the BMP data imgBmpStart, then we allocate a byte array buffer to get the BMP data from the binary reader.

We create a Memory Stream from the BMP data buffer and create a Bitmap from that memory stream. We have to use a trick to do so as will be explained below.

Finally we read the ending sentinel again for checking integrity.

BMP Structure

The BMP structure, as it is interpreted by the Bitmap class constructor when reading from a Stream, seems to be somewhat different from that of the actual data present in the DWG file.

Standard BMP Structure

  1. File Information header: a 14 bytes long header record, with the following structure:

    WORD bfType , It must be set to &H4D42, 
        to indicate that the file is a Bitmap
    DWORD bfSize , Total size of the file
    WORD bfReserved1
    WORD bfReserved2
    DWORD bfOffBits , Offset of the image data from start of the file
  2. Bitmap Information Header
    DWORD biSize , Size of the image header, should be 40.
    LONG biWidth , Width of the image in pixels.
    LONG biHeight , Height, this is also the stride of the bitmap.
    WORD biPlanes , number of bit planes, always 1
    WORD biBitCount , bits per pixel 1, 4, 8 or 24
    DWORD biCompression , 0 for no compression.
    DWORD biSizeImage , Size of image data
    LONG biXPelsPerMeter , X resolution
    LONG biYPelsPerMeter , Y resolution
    DWORD biClrUsed, Number of colours
    DWORD biClrImportant , number of colour indexes that are important. 
        If 0, all indexes are important.
  3. Colour palette
  4. Actual Image data

DWG BMP Structure

Tampering with the data present in the DWG binary file, we found that the File Information Header of the BMP is not present. So when passing the structure as a Memory Stream to the Bitmap constructor, we get an error of "Not a valid parameter passed."

So we came to a solution by creating this header in the first 14 bytes of the tempPixelData buffer. We set the bytes corresponding to the first WORD bfType, to &H4D42. Which is a constant indicating this a BMP file. And we set the bytes corresponding to the final WORD bfOffBits to &H0436, which is a constant for the 256 BMP image data offset.

Then we read the image data, append it to the the tempPixelData buffer array, after the header, creates a Memory Stream from it and pass it to the Bitmap constructor which is able to read the BMP as you can see in the sample image at top of this article.

Points of Interest

That's it! For a complete BMP structure, see How to Load a Bitmap by Mark Bernard.

History

  • Nov. 18, 2007 -- Original posting
  • Nov. 22, 2007 -- Image is fully interpreted

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here