A-Dam 07-12-2003, 01:41 AM The GetBitmapBits function is giving me trouble. I have a program that generates reports and sends the output to a Picturebox for viewing and/or the Printer for printing (duh). So the customer can save a copy of the report, I use SavePicture on the Picturebox's Image property. A report of about 8 pages results in a large (up to 600 x 7000 pixel) bitmap. This file would be 10 megs when created with the SavePicture function. I hate wasting that much disk space (even if it is the customer's disk)! A 1-bit bitmap results in a file about 400KB, almost as compact as a gif image. Everything works, except the byte array returned by GetBitmapBits is not always Long-aligned. I have to adjust the bitmap width to get the scan lines padded on the 4-byte boundary. I think that it is Integer-aligning, but I can't seem to make a valid bitmap file unless the scan lines are multiples of 4 bytes. The code is below. I hope this thread posts.
'' General declarations section (omitted) has
'' BITMAP type and API functions declared
Private Sub SavePictureBW()
Dim hdcMono As Long, hbmpMono As Long, hbmpOld As Long, dxBlt As Long, dyBlt As Long, success As Long
Dim num As Integer
Dim bmpsrc As BITMAP
Dim bitmaparray() As Byte
' Picturebox's ScaleMode is set to pixels
' Get the dimensions
dxBlt = pbSrc.ScaleWidth
dyBlt = pbSrc.ScaleHeight
' Create memory device context
hdcMono = CreateCompatibleDC(0)
' Create monochrome bitmap
hbmpMono = CreateCompatibleBitmap(hdcMono, dxBlt, dyBlt)
' Use GetObject to fill bmpsrc with bitmap info
success = GetBitmapObject(hbmpMono, LenB(bmpsrc), bmpsrc)
'''''''''''''''''''<<<<
' This code makes sure scan lines get padded correctly
' I would like to avoid changing the bitmap's width, but
' I can't figure out a better way
Do While (bmpsrc.bmWidthBytes Mod 4)
' Delete bitmap before creating another
Call DeleteObject(hbmpMono)
' Reduce bitmap width until it is Dword aligned
dxBlt = dxBlt - 1
' Create new mono bitmap
hbmpMono = CreateCompatibleBitmap(hdcMono, dxBlt, dyBlt)
' Use GetObject to fill bmpsrc with bitmap info
success = GetBitmapObject(hbmpMono, LenB(bmpsrc), bmpsrc)
Loop
'''''''''''''''''''<<<<
hbmpOld = SelectObject(hdcMono, hbmpMono)
' Copy picturebox's bitmap to DC to create mono mask
' Bitmap must be flipped vertically with StretchBlt or byte array will be "upside-down"
success = StretchBlt(hdcMono, 0, 0, dxBlt, dyBlt, pbSrc.hdc, 0, dyBlt - 1, dxBlt, -dyBlt, SRCCOPY)
ReDim bitmaparray(1 To (bmpsrc.bmWidthBytes * bmpsrc.bmHeight))
' Fill the byte array with the bit data
success = GetBitmapBits(hbmpMono, UBound(bitmaparray), bitmaparray(1))
' Here you would create the bitmap's file header data
' and save it all to disk as the finished black & white bitmap
' Clean up
' Select old bitmap back into dc first
Call SelectObject(hdcMono, hbmpOld)
Call DeleteDC(hdcMono)
Call DeleteObject(hbmpMono)
MsgBox "done"
End Sub
Thanks for any help.
A-Dam 07-12-2003, 09:50 AM No reply yet? I know some of you are bitmap gurus. You have "Blit" for a middle name. Any ideas? Will GetDiBits always Dword align?
Please Help.
beachcomber 07-12-2003, 10:34 AM Save the file in some compressed format such as jpg.
You can find the dll and bas modules for the declarations for the intell library at planet source code. The early versions which are posted are royality free and require no specific licensing requirements for use.
But I don't understand why you don't send your report to a control which is a bit more gdi friendly such as the richtextbox. You already are having to have to format the text - then you must play with the picturepox.image and hdc properties to refresh and paint etc..
Printing to the richtextbox, for instance, is just a manner of formatting you text into strings and sending it to the control. No gdi interfacing is required. Then just send the report to the printer, save the file with the control's built in features. That way the report can be viewed in a number of text applications and via a graphic application.
Using the flexgrid for instance has a picture property that can be set to monochrome (black & white) - that is an other option.
A-Dam 07-12-2003, 11:27 PM I knew someone would say to convert to jpeg or gif. I read up on the Intel Jpeg library quite a while ago. Back then I heard that it is no longer free, but that you can still find it and download it from certain places. I sold my program to a customer that does regular audits to make sure that there is no pirated software on the company computers. I wanted to avoid third-party controls.
I was considering using a richtextbox when I designed the program. You have good control over text formatting, and the ability to save as RTF files is definitely sweet. But I decided against the richtextbox for a few reasons. My reports are only in black and white. They don't need multiple font colors or faces or sizes (which you could get just as easily with a picturebox). One of the reports is typically between 5 and 10 pages. I output that to a scrollable Picturebox. I don't know if you will have troubles by making a richtextbox 100,000 twips in height. Probably not, but the Picturebox control gives the same output to the screen as you will get when you send that output to the printer. I've attached an example of my output:
You said:
Printing to the richtextbox, for instance, is just a manner of formatting you text into strings and sending it to the control. No gdi interfacing is required. Then just send the report to the printer...
How would you handle page headers or keeping detail groups from being split to separate pages?
You already are having to have to format the text - then you must play with the picturepox.image and hdc properties to refresh and paint etc..
My reports basically consist of Print and CurrentX statements. I don't have to refresh and paint. I only use the image and hdc properties when I am creating the bitmap file that this thread was addressing. If that Intel library will create a jpeg file from a picturebox's image, and if there are no licensing problems, then I might look into it. Thanks for the suggestions.
BUT, does anyone know about getting a bitmap's bits into a properly aligned array?
beachcomber 07-13-2003, 09:17 AM Ok - the older versions of the library are still free. If you go the the Intell website and do a bit of checking you will see that the earlier version is still free. They have resigned their control and changed its name for the newer version that requires licensing.
The older version usually is packed with the license agreement for the dll and that should address any issues your customer may have.
However ...
If there are issues then you can use a control that is shipped with all versions of windows (I don't know about xp etc) or can be downloaded from microsoft - imaging for windows.
It contains four controls basically and you can use them to save and change images. In fact a simple matter of some property settings to get a monochrome image conversion.
You can find them in the project component manager
Kodak Image Admin Control
Kodak Image Edit Control
Kodak Image Scan Control 'use to interface with a scanning device
Kodak Image Thumbnail Control
In your version of msdn you will find a complete manual (I mean manual) on using these controls.
You don't have to include these controls in your distribution package as they should be on all platforms after win95 automatically.
I don't like these controls myself as there are easier methods - but they can do what you want by setting a few properties, methods, and procedures built into them.
A-Dam 08-19-2003, 03:13 AM Here's a procedure for saving a picturebox's image as a black & white bmp file. I finally got back to that old project and figured it out. GetBitmapBits wasn't getting the bits like it should, at least I couldn't get it to work. GetDIBits did the trick.
This works well for saving text. All white pixels are saved as white. Anything other than white is saved as black. It doesn't do greyscale or dithering, so photos will come out pretty much all black. The suggestion to use the Intel jpeg library didn't suit my purpose. Saving a black & white image as a jpg still resulted in a huge file size. This procedure creates a file about 24 times smaller than the SavePicture statement.
Maybe someone can find use for it, so here it is.
Private Sub SavePictureBW(ByVal ctrl As PictureBox, ByVal destfile As String)
Dim hdcMono As Long, hbmpMono As Long, hbmpOld As Long, dxBlt As Long, dyBlt As Long, success As Long
Dim numscans As Long, byteswide As Long, totalbytes As Long, lfilesize As Long
Dim bmpsrc As BITMAP, bmpdst As BITMAP
Dim bInfo As BITMAPINFO
Dim bitmaparray() As Byte, fileheader() As Byte
Dim ff As Integer
'Object's scalemode must be Pixel.
dxBlt = ctrl.ScaleWidth
dyBlt = ctrl.ScaleHeight
'Create monochrome bitmap from control.
hdcMono = CreateCompatibleDC(0)
hbmpMono = CreateCompatibleBitmap(hdcMono, dxBlt, dyBlt)
success = GetBitmapObject(hbmpMono, Len(bmpsrc), bmpsrc)
hbmpOld = SelectObject(hdcMono, hbmpMono)
success = BitBlt(hdcMono, 0, 0, dxBlt, dyBlt, ctrl.hdc, 0, 0, SRCCOPY)
'Calculate array size needed for bitmap bits (dword aligned)
numscans = dyBlt
by8 = dxBlt / 8
If (dxBlt Mod 8) = 0 And (by8 Mod 4) = 0 Then
byteswide = by8
Else
byteswide = (Int(by8) + 4) - (Int(by8) Mod 4)
End If
totalbytes = numscans * byteswide
ReDim bitmaparray(1 To totalbytes)
'Set BITMAPINFO values to pass to GetDIBits function.
With bInfo
.bmiHeader.biSize = Len(.bmiHeader)
.bmiHeader.biWidth = bmpsrc.bmWidth
.bmiHeader.biHeight = bmpsrc.bmHeight
.bmiHeader.biPlanes = bmpsrc.bmPlanes
.bmiHeader.biBitCount = bmpsrc.bmBitsPixel
.bmiHeader.biCompression = BI_RGB
End With
success = GetDIBits(hdcMono, hbmpMono, 0, numscans, bitmaparray(1), bInfo, DIB_RGB_COLORS)
'bitmaparray should now contain bitmap bit data. Now create bitmap file header.
ReDim fileheader(1 To &H3E)
fileheader(1) = &H42 'B
fileheader(2) = &H4D 'M
lfilesize = UBound(fileheader) + UBound(bitmaparray)
fileheader(3) = lfilesize And 255
fileheader(4) = (lfilesize \ 256) And 255
fileheader(5) = (lfilesize \ 65536) And 255
fileheader(6) = (lfilesize \ 16777216) And 255
fileheader(11) = &H3E 'offset
fileheader(15) = &H28 'size of bitmapinfoheader
fileheader(19) = dxBlt And 255
fileheader(20) = (dxBlt \ 256) And 255
fileheader(21) = (dxBlt \ 65536) And 255
fileheader(22) = (dxBlt \ 16777216) And 255
fileheader(23) = dyBlt And 255
fileheader(24) = (dyBlt \ 256) And 255
fileheader(25) = (dyBlt \ 65536) And 255
fileheader(26) = (dyBlt \ 16777216) And 255
fileheader(27) = 1
fileheader(29) = 1
fileheader(35) = UBound(bitmaparray) And 255
fileheader(36) = (UBound(bitmaparray) \ 256) And 255
fileheader(37) = (UBound(bitmaparray) \ 65536) And 255
fileheader(38) = (UBound(bitmaparray) \ 16777216) And 255
fileheader(47) = 2
fileheader(51) = 2
fileheader(59) = &HFF
fileheader(60) = &HFF
fileheader(61) = &HFF
ff = FreeFile
Open destfile For Binary Access Write As #ff
Put #ff, , fileheader
Put #ff, , bitmaparray
Close #ff
' Clean up
Call SelectObject(hdcMono, hbmpOld)
Call DeleteDC(hdcMono)
Call DeleteObject(hbmpMono)
End Sub
OnErr0r 08-19-2003, 08:59 AM For your byte alignment you could use:
by8 = (dxBlt + 7) \ 8 ' byte align bits
byteswide = (by8 + 3) And Not 3 ' dword align bytes
A-Dam 08-19-2003, 11:30 PM Thank you. I was looking for a cleaner way to do that. In the documentation, I found this a lot:
biSizeImage = ((((biWidth * biBitCount) + 31) &
~31) >> 3) * biHeight
Since it was written for C, I didn't know how to translate the shifts into VB code. Well, I guess I did figure it out in my own way. That's what a noob is supposed to do, right? :D
OnErr0r 08-19-2003, 11:54 PM That would work well too. Bitcount is 1, so I guess this would be correct:
byteswidth = ((dxBlt + 31) And Not 31) \ 8
Note that / is floating point division, and \ is integer division, suitable for shifting.
>> is right shift and each number of bits shifted is equivalent to integer division by 2 ^ number of bits.
2 ^ 1 = 2 (>> 1)
2 ^ 2 = 4 (>> 2)
2 ^ 3 = 8 (>> 3)
|