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:
- File -> New -> New project
- Create a New
VBDwgImageExtractor
Visual Basic Windows Application
- Change the
Form1
Text field to VBDwgImageExtractor
- From the
Toolbox
, drag-n-drop a PictureBox
into the form
- Drag-n-drop a
Button
and an openFileDialog
into the form
- Set the
openFileDialog1
Filter property to Drawing files (*.dwg)|*.dwg|All files (*.*)|*.*
, and the DefaultExt
to dwg
- 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
Dim fs As FileStream = _
New FileStream(path, FileMode.Open, FileAccess.Read,
FileShare.ReadWrite)
Dim r As BinaryReader = New BinaryReader(fs)
r.BaseStream.Seek(&HD, SeekOrigin.Begin)
Dim imgPos As Int32 = r.ReadInt32()
r.BaseStream.Seek(imgPos, SeekOrigin.Begin)
Dim imgBSentinel() As Byte = _
{&H1F, &H25, &H6D, &H7, &HD4, &H36, &H28, _
&H28, &H9D, &H57, &HCA, &H3F, &H9D, &H44, &H10, &H2B}
Dim imgCSentinel(16) As Byte
imgCSentinel = r.ReadBytes(16)
If (imgBSentinel.ToString() = imgCSentinel.ToString()) Then
Dim imgSize As UInt32 = r.ReadUInt32()
Dim imgPresent As Byte = r.ReadByte()
Dim imgHeaderStart As Int32 = 0
Dim imgHeaderSize As Int32 = 0
Dim imgBmpStart As Int32 = 0
Dim imgBmpSize As Int32 = 0
Dim bmpDataPresent As Boolean = False
Dim imgWmfStart As Int32
Dim imgWmfSize As Int32
Dim wmfDataPresent As Boolean = False
For I As Integer = 1 To imgPresent
Dim imgCode As Byte = r.ReadByte()
Select Case imgCode
Case 1
imgHeaderStart = r.ReadInt32()
imgHeaderSize = r.ReadInt32()
Case 2
imgBmpStart = r.ReadInt32()
imgBmpSize = r.ReadInt32()
bmpDataPresent = True
Case 3
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
tempPixelData(0) = &H42
tempPixelData(1) = &H4D
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
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 (imgESentinel.ToString() = imgCSentinel.ToString()) Then
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
-
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
- 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.
- Colour palette
- 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