Asim-GDI GURU Level: Sage
 Registered: 29-07-2005 Posts: 54
|
DIB SECTION
Windows offers a lot of ways to handle a bitmap. Every way with its own advantages and painful disadvantages. According to Windows Bitmap is classified into two types namely : Device Dependent Bitmpas and Device Independent Bitmaps. Primarilly both the types store themself in memory,if any other place is not defined as the destination for the data to be kept.Device Dependent Bitmaps (DDB) are those which are all look after by Windows itself from the creation to their demise but only for one particular device as the name suggests. It means that if a DDB is made according to one device then it won't work on any other device. Like if once you create a DDB on a device with certain internal settings, the DDB will only work on this device or on the devices which are of same capablities. But the biggest drawback of using DDB is that they can not be saved on to disk. This is where a practical programmer says goodbye to DDB seeking for some independance. Device Independant Bitmaps (DIB) are what practical GDI programmers mostly deal with. DIBs are nothing more than a bunch of structures containing certain settings that Windows needs the programmer to set before making the DIB. But when we talk about DIBs we mean a lot of restrictions like you can't directly draw on it using the standard GDI API (LineTo,Ellipse). Thus it make DIBs another painful way to handle the bitmap. So here the idea should come to your mind of something that has the advantages of both but no disadvantages. Win32 addressed this issue so ellegantly that it made a third classification of Bitmap which is really matches with your idea. DIB Section is a type of Bitmap which is organized on the same structures DIBs are managed on but with the facilities of DDBs. It can be created using a single API, it can be selected into any device regardless of the device type, its pixel data can be accessed using just one API, it is friendly to all the GDI drawing API, it can be saved to disk, and what not.
Without going into any further details let me show how to make a DIB Section.
Option Explicit
' DECLARATIONS...
' Types Declarations
Private Type RGBQUAD
rgbBlue As Byte
rgbGreen As Byte
rgbRed As Byte
rgbReserved As Byte
End Type
Private Type BITMAPINFOHEADER
biSize As Long
biWidth As Long
biHeight As Long
biPlanes As Integer
biBitCount As Integer
biCompression As Long
biSizeImage As Long
biXPelsPerMeter As Long
biYPelsPerMeter As Long
biClrUsed As Long
biClrImportant As Long
End Type
Private Type BITMAPINFO
bmiHeader As BITMAPINFOHEADER
bmiColors As RGBQUAD
End Type
' API Declarations
Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, ByVal X As Long, ByVal Y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function SelectObject Lib "gdi32.dll" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long
Private Declare Function GetDIBits Lib "gdi32" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFOHEADER, ByVal wUsage As Long) As Long
Private Declare Function SetDIBits Lib "gdi32" (ByVal hdc As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFOHEADER, ByVal wUsage As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare Function CreateDIBSection Lib "gdi32.dll" (ByVal hdc As Long, ByRef pBitmapInfo As BITMAPINFO, ByVal un As Long, lplpVoid As Long, ByVal handle As Long, ByVal dw As Long) As Long
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Declare Function SetPixelV Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long, ByVal crColor As Long) As Long
Private Declare Function GetTickCount Lib "kernel32.dll" () As Long
' Constants Declarations
Private Const BI_RGB = 0&
Private Const DIB_RGB_COLORS = 0
Private Const SRCCOPY = &HCC0020
' Personal Declaration
Private bDIB() As RGBQUAD
Private BI As BITMAPINFO
Private DC As Long
Private DIBSection As Long
Private oldDIB As Long
Private Sub Form_Load()
' Creating the HDC to contain the DIB Section
DC = CreateCompatibleDC(Picture1.hdc)
If (DC <> 0) Then ' Checking whether the DC is made or not
' Finally making the 6 settings required by the programmer to set
With BI.bmiHeader
.biSize = 40
.biWidth = Picture1.ScaleWidth ' Picture Width
.biHeight = -Picture1.ScaleHeight ' Picture Height(dont confuse with the (-) sign.Just make it compulsory)
.biPlanes = 1
.biBitCount = 32
.biSizeImage = (((Picture1.ScaleWidth * 3) + 3) And &HFFFFFFFC) * Picture1.ScaleHeight ' This is how we can calculate the Image Size in Bytes
End With
Dim addr As Long ' This will contain the memory address to the Bitmap data in the memory
' And now making the EMPTY DIB Section with above settings
DIBSection = CreateDIBSection(DC, BI, DIB_RGB_COLORS, addr, 0, 0)
If (DIBSection <> 0) Then ' Checking whether the DIB Section is made or not
' Selecting the DIB Section into the DC to do drawing on it
oldDIB = SelectObject(DC, DIBSection)
Else
Call DeleteDC(DC) ' If the DIB Section could not be made then delete the DC we made earlier
End If
End If
End Sub
' Now the DIB Section is made and ready to be used. You can do usuall GDI drawing on it or can
' do special stuff like passing the Pixel data through a Image Filter(like GreyScale)
' I'll show you two ways to handle the allready made DIB Section
Private Sub Grey_Click()
Dim X As Long, Y As Long, ti As Long, Pixel As Long, GrayScale As Long
ti = GetTickCount
' Now copying the Picturebox Image in the EMPTY DIB Section
Call BitBlt(DC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hdc, 0, 0, SRCCOPY)
' We have to go through each and every pixel in the Bitmap
For X = 0 To Picture1.ScaleWidth
For Y = 0 To Picture1.ScaleHeight
Pixel = GetPixel(DC, X, Y) ' Get one pixel from the DIB Section
' Now we process the acquired pixel
GrayScale = ((77& * (Pixel And &HFF&) + 152& * (Pixel And &HFF00&) \ &H100& + 28& * (Pixel \ &H10000)) \ 256&) * &H10101
' Then setting back the processed pixel in the DIB Section
Call SetPixelV(DC, X, Y, GrayScale)
Next Y
Next X
' Copying the processed picture into the Picturebox
Call BitBlt(Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, DC, 0, 0, SRCCOPY)
MsgBox "Operation completed in " & GetTickCount - ti & " ms"
End Sub
Private Sub Grey1_Click()
Dim X As Long, Y As Long, ti As Long
ti = GetTickCount
' Now copying the Picturebox Image in the EMPTY DIB Section
Call BitBlt(DC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hdc, 0, 0, SRCCOPY)
' Getting the Pixel Array of the DIB Section
ReDim bDIB(1 To Picture1.ScaleWidth, 1 To Picture1.ScaleHeight) As RGBQUAD
' The following API is the ONE which gets the Pixel Array of the DIB Section
Call GetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
' We have to go through each and every pixel in the Bitmap
For X = 1 To Picture1.ScaleWidth
For Y = 1 To Picture1.ScaleHeight
bDIB(X, Y).rgbBlue = 0.3 * bDIB(X, Y).rgbRed + 0.587 * bDIB(X, Y).rgbGreen + 0.114 * bDIB(X, Y).rgbBlue
bDIB(X, Y).rgbGreen = bDIB(X, Y).rgbBlue
bDIB(X, Y).rgbRed = bDIB(X, Y).rgbBlue
Next Y
Next X
' Setting back the Pixel Array of the DIB Section
Call SetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
' Copying the processed picture into the Picturebox
Call BitBlt(Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, DC, 0, 0, SRCCOPY)
MsgBox "Operation completed in " & GetTickCount - ti & " ms"
End Sub
Private Sub Negative_Click()
Dim X As Long, Y As Long, ti As Long
ti = GetTickCount
' Now copying the Picturebox Image in the EMPTY DIB Section
Call BitBlt(DC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hdc, 0, 0, SRCCOPY)
' Getting the Pixel Array of the DIB Section
ReDim bDIB(1 To Picture1.ScaleWidth, 1 To Picture1.ScaleHeight) As RGBQUAD
' The following API is the ONE which gets the Pixel Array of the DIB Section
Call GetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
' We have to go through each and every pixel in the Bitmap
For X = 1 To Picture1.ScaleWidth
For Y = 1 To Picture1.ScaleHeight
' The following is the way we can calculate the negative shade of every pixel in the DIB Section
' Just Red = 255 - Red , Green = 255 - Green , Blue = 255 - Blue
bDIB(X, Y).rgbBlue = 255 - bDIB(X, Y).rgbBlue
bDIB(X, Y).rgbGreen = 255 - bDIB(X, Y).rgbGreen
bDIB(X, Y).rgbRed = 255 - bDIB(X, Y).rgbRed
Next Y
Next X
' Setting back the Pixel Array of the DIB Section
Call SetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
' Copying the processed picture into the Picturebox
Call BitBlt(Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, DC, 0, 0, SRCCOPY)
MsgBox "Operation completed in " & GetTickCount - ti & " ms"
End Sub
' The following part is the most important one in the whole script
' because if its omited then a Windows Crash(especially in NT)
' is highly expected
' Actually here we free all the memory we ever used in the script
Private Sub Form_Unload(Cancel As Integer)
Erase bDIB()
Call SelectObject(DC, oldDIB) ' Good Programming Practice
Call DeleteObject(DIBSection) ' Deleting the DIB Section
Call DeleteDC(DC) ' Deleting the container DC
End Sub
|
The script is commented alot just to make it easier for the reader to understand how we created the DIB Section and how we are using it in different ways. The normal time the Negative and Fast Grey Scale function takes on my system(AMD Sempron, 1.20 GHz, 192 MB RAM) is something like 16 ms when processing a 420x270 bitmap. Now lets discuss the creation of the DIB Section.
The Header Size is always 40 bytes but the programmer can obviously use len(BI.bmiHeader).
BI.bmiHeader.biSize = 40
The following, as the comment says, is the width of the Bitmap(in pixels). Simply the programmer is defining the dimensions of the bitmap he want in the next two lines.
BI.bmiHeader.biWidth = Picture1.ScaleWidth ' Picture Width
Device Independent Bitmaps are always upside-down, so to get it in the correct allignment we specify a (-) minus sign with the height. If you dont understand what I just said then just make is compulsory to put the minus sign with the height.
BI.bmiHeader.biHeight = -Picture1.ScaleHeight
biPlanes specifies the number of planes for the target device. This value must be set to 1.
BI.bmiHeader.biPlanes = 1
The following line makes a lot of meaning in this whole procedure. Actually this is the main thing that Windows needs to create the DIB. This specifies the number of bits-per-pixel. It determines the number of bits that define each pixel and the maximum number of colors in the bitmap. This member must be 32 in our case. I dont want to confuse the reader thats why not dealing with the 24Bit Image structure.
BI.bmiHeader.biBitCount = 32
Calculating the size of the Bitmap is one of the most painful part of the code, I feel. Just one miss you do and your code wont work. Actually in the following line we are not only calculating the size, rather we are also making it compatible with 32 bit boundary. This is neccessary so do it in the same way in your projects.
BI.bmiHeader.biSizeImage = (((Picture1.ScaleWidth * 3) + 3) And &HFFFFFFFC) * Picture1.ScaleHeight
For practical programmer the following variable's value is of more importance than the real DIB Section. By good programing techniques we can get the DEFAULT array of the Bitmap, and then need of refreshing the array and the DIB becomes meaningless. But at this level this is not usefull to you. Just dont confuse with this variable and believe that this is another setting.
Dim addr As Long
In the comments I said we are now finally making an EMPTY DIB Section. Empty really means empty, meaning that its Pixel Array contains nothing. Its just like a newly constructed house which is not painted yet.
DIBSection = CreateDIBSection(DC, BI, DIB_RGB_COLORS, addr, 0, 0)
By now we have seen how we can create an EMPTY DIB Section. Now we'll see how to paint our empty house. This is done by simple BitBlt API (Bit-Block Transfer) which is nothing but the VB PaintPicture command but very faster than the VB command. Actually we are just copying the image present in the Picturebox to the EMPTY DIB Section. Its like our Picturebox is the Paint Box and BitBlt API is the brush, by using which we paint the empty house.
Call BitBlt(DC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hdc, 0, 0, SRCCOPY)
Now how to access the pixels in the DIB Section. A very common way is always to go through a fool's way, i.e. GetPixel & SetPixel technique. I'll not waste time in describing that part of the script. I'll directly jump to the part where we get the pixel array and process it directly like good professional programmers.
First we are using the BitBlt to copy the image in the Picturebox to the EMPTY DIB Section.
Call BitBlt(DC, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, Picture1.hdc, 0, 0, SRCCOPY)
Now we are defining our local array to the dimensions of the bitmap. This is neccessary.
ReDim bDIB(1 To Picture1.ScaleWidth, 1 To Picture1.ScaleHeight) As RGBQUAD
Finally Getting the Pixel Array from the PAINTED DIB Section to our local bDIB() array.
Call GetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
Now going through each and every pixel to process it.
For X = 1 To Picture1.ScaleWidth
For Y = 1 To Picture1.ScaleHeight
The following is the standard filter for making a picture look its negative.
bDIB(X, Y).rgbBlue = 255 - bDIB(X, Y).rgbBlue
bDIB(X, Y).rgbGreen = 255 - bDIB(X, Y).rgbGreen
bDIB(X, Y).rgbRed = 255 - bDIB(X, Y).rgbRed |
And now setting back a copy of our local processed pixel array (bDIB()) to the original DIB Section Pixel Array. This is the way we refresh the DIB Section original array. This is the most annoying part of doing Bits Editing in the way I did it here. I obviously have another very very easy and 10 times faster way to do it using memory manipulation but that would not be a wise idea to introduce it at this moment to the reader of all level.
Call SetDIBits(DC, DIBSection, 0&, Picture1.ScaleHeight, bDIB(1, 1), BI.bmiHeader, 0&)
Everything done. The DIB Section origianl array is refreshed according to our local processed array. So now we'll copy the entire image of the DIB Section to the Picturebox.
Call BitBlt(Picture1.hdc, 0, 0, Picture1.ScaleWidth, Picture1.ScaleHeight, DC, 0, 0, SRCCOPY)
The comments that the most important job of a good programmer is to remove all the memory space he ever acquired in his script at the end of the program. So we are gonna do it in the Form_Unlod subroutine.
Somebody once said to me that erasing the VB made arrays is not that important as VB will do the job itself, but I beleive if I must create it then I must destroy it in the end.
Erase bDIB()
In the following line we select out the DIB Section of the DC so that we can delete the DC( Deleting the empty DC is a good programing practice).
Call SelectObject(DC, oldDIB)
Now in the following two lines, we are deleting the DIB Secion using DeleteObject and then deleting the DC using DeleteDC. This actually tells you that yes DIB Section is a Windows object and you are required to delete it in the end.
Call DeleteObject(DIBSection) ' Deleting the DIB Section
Call DeleteDC(DC) ' Deleting the container DC
So by now I suppose I described the whole lot of code good. And was able to make you understand. This thing is very easy to do and Windows provide a lot of help to the developers ( novice to expert ) in many means. Like there are articles, example (in your favourite languages), MSDN Library, and very comprehensive forums, all available at the Microsoft site. Just kick start your mind to go in this direction who knows Windows will introduce something better than DIB Section in Windows Vista.
[Edited by Asim-GDI GURU on 17-07-2006 at 04:13 AM GMT]
|