I've written a VBA function to return the width of a string in a given font & point size at 300dpi. I'm not a very experienced programmer, and this is my first use of Windows API. I would expect the width to decrease gradually with font size, but its not so gradual.
For example:
Point Size = Returned Width (string "Text" in font Arial)
14 = 99
13.5 = 95
13 = 91
12.5 = 90
12 = 84
11.5 = 83
11 = 75
So reducing font size by 0.5 changes width by 4>4>1>6>1>8. I'd like to understand why there's a non-linear relationship between font size and returned width. I know there's vaguery in font rendering, but I'd guess that's not the whole story here, especially not at 300dpi?
Like I said, I'm a not-quite-novice so feel free to say "Google [keyword], scrub!"
Context: Unlike most uses of GetTextExtent, there is no object I'm trying to fit. End goal is a function to return whether string A at X points is wider than string B at Y points when printed in font F at 300dpi.
Here's my code collapsed to one function...
Option Explicit
Private Declare PtrSafe Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, ByVal lpInitData As Long) As Long
Private Declare PtrSafe Function CreateFont Lib "gdi32.dll" Alias "CreateFontA" (ByVal nHeight As Integer, ByVal nWidth As Integer, ByVal nEscapement As Integer, ByVal nOrientation As Integer, ByVal fnWeight As Integer, ByVal fdwItalic As Long, ByVal fdwUnderline As Long, ByVal fdwStrikeOut As Long, ByVal fdwCharSet As Long, ByVal fdwOutputPrecision As Long, ByVal fdwClipPrecision As Long, ByVal fdwQuality As Long, ByVal fdwPitchAndFamily As Long, ByVal lpszFace As String) As Long
Private Declare PtrSafe Function SelectObject Lib "gdi32.dll" (ByVal hDC As Long, ByVal hObject As Long) As Long
Private Declare PtrSafe Function GetTextExtentPoint32 Lib "gdi32.dll" Alias "GetTextExtentPoint32A" (ByVal hDC As Long, ByVal lpctStr As String, ByVal c As Integer, ByRef sz As SIZE) As Boolean
Private Declare PtrSafe Function DeleteDC Lib "gdi32.dll" (ByVal hDC As Long) As Long
Private Declare PtrSafe Function DeleteObject Lib "gdi32.dll" (ByVal hObject As Long) As Long
Private Type SIZE
x As Long
y As Long
End Type
Function GetPrintedWidth(strToTest As String, strFontName as String, sngFontSize As Single)
'Create the device context. (Documents are rendered to PDF before printing, so I'm using the Adobe PDF printer driver. Mistake?)
Dim DC As Long: DC = CreateDC(0, "Adobe PDF", 0, 0)
'Convert sngFontSize points to logical units. Final print is #300dpi.
'Most examples I've seen use MulDiv, but it converts sngFontSize to long before calculating.
Dim nHeight As Long: nHeight = sngFontSize * 300 / 72
'Create the font.
Dim fnt As Long: fnt = CreateFont(nHeight, 0, 0, 0, 400, 0, 0, 0, 0, 0, 0, 0, 0, strFontName & Chr$(0))
'Select font into DC.
DeleteObject SelectObject(DC, fnt)
'Get string dimensions.
Dim sz As SIZE: GetTextExtentPoint32 DC, strToTest, Len(strToTest), sz
'Return width.
GetPrintedWidth = sz.x
'Clean up.
DeleteObject fnt
DeleteDC DC
End Function
I'm extremely open to basic code corrections, I'm still learning!
Y'all are the best, thanks for everything :)
Update
Increasing nHeight smooths things out. I get nearly linear results going from:
nHeight = sngFontSize * 300 / 72
to:
nHeight = sngFontSize * 3000 / 72
So... I guess it is just a font scaling issue, but I'm completely in the dark as to why. AFAIK the nHeight formula should be
FontSize * PointsPerLogicalInch / 72
...ergo a 300dpi printer should have 300 PointsPerLogicalInch, no?
Update 2
GetDeviceCaps LOGPIXELSY is usually used to get PointsPerLogicalInch. I tested my available printers:
Adobe PDF print driver returned 1200
My desk printer returned 600
Incidentally, LOGPIXELSX returned the same
So 300 should be right or I've bungled something.
Related
I have an Access 365 64-bit problem I am trying to solve and I need some guidance.
I have a byte array with image data, retrieved from a Base64 encoded string. The decoding is working just fine and I can produce the array as expected. I have had this code from an older 32 bit version of VBA applications and it remains fully functional. So far so good.
The issue comes into play when I am trying to place the image data into a forms image control directly - no saving to a file. I have had this working in the 32 bit applications but now that the office 365 subscription I am working with is 64 bit (as is the rest of the company) I am having trouble converting the API calls and subsequent code across to 64-bit compatible versions. The images are largely PNG and JPG images (when extracted).
The main issue seems to be coming from the need to replace OLEPRO32 with OLEAUT32. The code runs (seemingly) without error however I am not getting any output. I am sure that I am missing something simple but I just cannot see it. My code is below as well as the API declarations I am using, along with the old OLEPRO32 declaration which is commented out.
Option Explicit
Option Compare Database
Declare PtrSafe Function CreateStreamOnHGlobal Lib "ole32" (ByVal hGlobal As LongPtr, ByVal fDeleteOnRelease As Long, ppstm As Any) As LongPtr
Declare PtrSafe Function GlobalAlloc Lib "kernel32" (ByVal uFlags As Long, ByVal dwBytes As Long) As LongPtr
Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
'Declare PtrSafe Function OleLoadPicture Lib "olepro32" (pStream As Any, ByVal lSize As Long, ByVal fRunmode As Long, riid As Any, ppvObj As Any) As LongPtr
Declare PtrSafe Function OleLoadPicture Lib "oleaut32" (pStream As Any, ByVal lSize As Long, ByVal fRunmode As Long, riid As Any, ppvObj As Any) As LongPtr
Declare PtrSafe Sub CopyMemory Lib "kernel32.dll" Alias "RtlMoveMemory" (ByRef Destination As Any, ByRef Source As Any, ByVal Length As Long)
And here is the routine that uses the declarations:
Public Function ArrayToPicture(inArray() As Byte, Offset As Long, Size As Long) As IPicture
' function creates a stdPicture from the passed array
' Offset is first item in array: 0 for 0 bound arrays
' Size is how many bytes comprise the image
Dim o_hMem As LongPtr
Dim o_lpMem As LongPtr
Dim aGUID(0 To 3) As Long
Dim IIStream As IUnknown
aGUID(0) = &H7BF80980 ' GUID for stdPicture
aGUID(1) = &H101ABF32
aGUID(2) = &HAA00BB8B
aGUID(3) = &HAB0C3000
o_hMem = GlobalAlloc(&H2&, Size)
If Not o_hMem = 0& Then
o_lpMem = GlobalLock(o_hMem)
If Not o_lpMem = 0& Then
CopyMemory ByVal o_lpMem, inArray(Offset), Size
Call GlobalUnlock(o_hMem)
If CreateStreamOnHGlobal(o_hMem, 1&, IIStream) = 0& Then
Call OleLoadPicture(ByVal ObjPtr(IIStream), 0&, 0&, aGUID(0), ArrayToPicture)
End If
End If
End If
End Function
If anyone has any ideas please let me know. The output of this function seems to be nothing at all whereas in the past I could expect a valid iPicture object that could be assigned directly to the form image controls .PictureData.
Any guidance greatly appreciated.
Cheers
The Frog
UPDATE:
I have worked through a large portion of the code and can now specifically isolate the locations where the crashes are happening. Code is below
Option Compare Database
' API declarations
Private Declare PtrSafe Function CreateStreamOnHGlobal Lib "Ole32.dll" (ByRef hGlobal As LongPtr, ByVal fDeleteOnRelease As Long, ByRef ppstm As LongPtr) As Long
Private Declare PtrSafe Function CLSIDFromString Lib "Ole32" (ByVal lpsz As LongPtr, pclsid As Any) As Long
Private Declare PtrSafe Function OLELoadPicture Lib "OleAut32.lib" (ByRef lpStream As LongPtr, ByVal lSize As Long, ByVal fRunMode As Long, ByRef RIID As GUID, ByRef lplpObj As LongPtr) As Long
Private Declare PtrSafe Function GetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As LongPtr, ByVal nCount As Long, lpObject As Any) As Long
Private Declare PtrSafe Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdc As LongPtr, ByVal nWidth As Long, ByVal nHeight As Long) As LongPtr
Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hdc As LongPtr, ByVal hObject As LongPtr) As LongPtr
Private Declare PtrSafe Function GetDIBits Lib "gdi32" (ByVal aHDC As LongPtr, ByVal hBitmap As LongPtr, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long
Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As LongPtr) As LongPtr
Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hdc As LongPtr) As Long
Private Declare PtrSafe Function GetWindowDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalAlloc Lib "kernel32" (ByVal wFlags As Long, ByVal dwBytes As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalLock Lib "kernel32" (ByVal hMem As LongPtr) As LongPtr
Private Declare PtrSafe Function GlobalUnlock Lib "kernel32" (ByVal hMem As LongPtr) As Long
Private Declare PtrSafe Sub MoveMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
Private Declare PtrSafe Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" (Destination As Any, Source As Any, ByVal Length As LongPtr)
'Necessary Types
Private Type BITMAPINFOHEADER '40 Bytes
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 RGBQUAD
rgbBlue As Byte
rgbGreen As Byte
rgbRed As Byte
rgbReserved As Byte
End Type
Private Type BITMAPINFO
bmiHeader As BITMAPINFOHEADER
bmiColors As RGBQUAD
End Type
Private Type DIBHEADER '14 magical bytes
BmpIdentification(1) As Byte
BmpSize(3) As Byte
BmpCreator As Integer
BmpCreator2 As Integer
BmpDataOffset(3) As Byte
End Type
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(7) As Byte
End Type
Public Function StdPictureToDibImage(ByRef Picture As StdPicture, Optional PadColor As Integer = -1) As Byte()
' Performs StdPicture to DIB compliant Byte Array
' Adopted based on : http://www.vbforums.com/showthread.php?833125-How-to-convert-StdPicture-into-pixel-array
' The Byte Arrays to Hold the Initial PictureData along with the Final One
Dim ImageData() As Byte
Dim buffer() As Byte
Dim tmp() As Byte
' Type Instances
Dim BMI As BITMAPINFO
Dim DIB As DIBHEADER
Dim PaddingColor As Integer
' API handle
Dim hdc As LongPtr
Dim hpic As LongPtr
hdc = CreateCompatibleDC(0) 'Create a temporary in-memory device context
BMI.bmiHeader.biSize = Len(BMI.bmiHeader) 'Initialize BitmapInfoHeader with header size
'Get the header Info of the Image based on the StdPicture handle provided
GetDIBits hdc, Picture.handle, 0, 0, ByVal 0&, BMI, 0 'Get Information about the image
'Normally here we would setup the header for BMI header but i found out that simply is not working as it should
' Only the DIB header needs manual handling
With DIB
.BmpIdentification(0) = 66
.BmpIdentification(1) = 77
tmp = LongToByteArray(BMI.bmiHeader.biSizeImage + Len(DIB) + Len(BMI.bmiHeader))
.BmpSize(0) = tmp(0)
.BmpSize(1) = tmp(1)
.BmpSize(2) = tmp(2)
.BmpSize(3) = tmp(3)
.BmpCreator = 0
.BmpCreator2 = 0
tmp = LongToByteArray(Len(DIB) + Len(BMI.bmiHeader))
.BmpDataOffset(0) = tmp(0)
.BmpDataOffset(1) = tmp(1)
.BmpDataOffset(2) = tmp(2)
.BmpDataOffset(3) = tmp(3)
End With
'Byte Arrays Initialization
ReDim ImageData(3, BMI.bmiHeader.biWidth - 1, BMI.bmiHeader.biHeight - 1) 'Initialize array for holding pixel data
ReDim buffer(0 To BMI.bmiHeader.biSizeImage + (Len(DIB) + Len(BMI.bmiHeader)) - 1)
'Here we get the actual Image Data from the StdPicture
'This was the most troubled part of the whole process as it kept truncating the image to around 3/4
' no matter what....until i noticed that the info "feeded" to the BMI header was wrong
hpic = CLngPtr(Picture.handle)
GetDIBits hdc, hpic, 0, Abs(BMI.bmiHeader.biHeight), ImageData(0, 0, 0), BMI, 0 'Get pixel data
'GetDIBits hdc, Picture.handle, 0, Abs(BMI.bmiHeader.biHeight), ImageData(0, 0, 0), BMI, 0 'Get pixel data
'Constructing the Final Image Data
'1st the DIB header ***** CRUCIAL ******* , without this everything fails and burns
CopyMemory buffer(0), DIB, Len(DIB)
'2nd the BMP header, this was done in all other cases
CopyMemory buffer(Len(DIB)), BMI.bmiHeader, Len(BMI.bmiHeader)
'3rd the actual image data
CopyMemory buffer(Len(DIB) + Len(BMI.bmiHeader)), ImageData(0, 0, 0), 3 * (BMI.bmiHeader.biWidth - 1) * (BMI.bmiHeader.biHeight - 1)
'Cleaning up
DeleteDC hdc 'Get rid of temporary in-memory device context
'Some Padding to remove the "dead" space because Images Dimensions are "resized" to multiple of 4s
'so if the either of the dimensions is not exactly a multiple of 4 then padding is applied which
'results is "dead" pixels
If PadColor < 0 Then
PaddingColor = 255
Else
PaddingColor = PadColor
End If
For I = UBound(buffer) To LBound(buffer) Step -1
If buffer(I) = 0 Then
buffer(I) = PaddingColor
Else
Exit For
End If
Next
'The final magical byte array...no more temp files,links,extra controls...whatever..everything in memory
StdPictureToDibImage = buffer()
End Function
Public Function PictureFromByteStream(ByRef B() As Byte) As IPicture
Dim LowerBound As Long
Dim ByteCount As Long
Dim hMem As Long
Dim lpMem As Long
Dim IID_IPicture As GUID
Dim istm As stdole.IUnknown
On Error GoTo Err_Init
If UBound(B, 1) < 0 Then
Exit Function
End If
LowerBound = LBound(B)
ByteCount = (UBound(B) - LowerBound) + 1
hMem = GlobalAlloc(&H2, ByteCount)
If hMem <> 0 Then
lpMem = GlobalLock(hMem)
If lpMem <> 0 Then
MoveMemory ByVal lpMem, B(LowerBound), ByteCount
Call GlobalUnlock(hMem)
If CreateStreamOnHGlobal(hMem, 1, istm) = 0 Then
If CLSIDFromString(StrPtr("{7BF80980-BF32-101A-8BBB-00AA00300CAB}"), IID_IPicture) = 0 Then
Call OLELoadPicture(ByVal ObjPtr(istm), ByteCount, 0, IID_IPicture, PictureFromByteStream)
End If
End If
End If
End If
Exit Function
Err_Init:
If err.Number = 9 Then
Debug.Print err.Number & " - " & err.Description
End Function
Function ArrayToStdPicture(imageBytes() As Byte) As StdPicture
Dim W As WIA.Vector
Dim s As StdPicture
Set W = New WIA.Vector
W.BinaryData = imageBytes
Set ArrayToStdPicture = W.Picture
If Not W Is Nothing Then Set W = Nothing
End Function
The process is as follows:
ArrayToStdPicture -> StdPictureToDIBImage
The crash occurs on the line:
GetDIBits hdc, hpic, 0, Abs(BMI.bmiHeader.biHeight), ImageData(0, 0, 0), BMI, 0 'Get pixel data
The crash totally crashes MS Access and kills the application.
I've tried the below code to adjust the screen size of Excel
Sub win()
Dim myWindow1 As Window, myWindow2 As Window
Set myWindow1 = ActiveWindow
Set myWindow2 = myWindow1.NewWindow
With myWindow1
.WindowState = xlNormal
.Top = 0
.Left = 0
.Height = Application.UsableHeight
.Width = Application.UsableWidth * 0.25
End With
With myWindow2
.WindowState = xlNormal
.Top = 0
.Left = (Application.UsableWidth * 0.25) + 1
.Height = Application.UsableHeight
.Width = Application.UsableWidth * 0.75
End With
End Sub
But i want to change the screen size of google chrome. How can i do without opening new Chrome application using shell ?
I want to change the screen size of already opened Chrome Application
You can use functions from the User32-library to control external windows. Here is an example for doing so for a Google Chrome "New Tab" window:
Option Explicit
Private Declare Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare Sub SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long)
Private Sub Resize_Chrome()
Dim ChromeHandle As Long
ChromeHandle = FindWindow(vbNullString, "New Tab - Google Chrome")
SetWindowPos ChromeHandle, -1, 0, 0, 600, 600, &H10
End Sub
This sets the window to the upper left corner (0,0) with a 600 x 600 pixel size (600,600)
For more information on the SetWindowPos function, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms633545%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396
Update to 2022 and 64bit.
Option Explicit
Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As Long
Private Declare PtrSafe Sub SetWindowPos Lib "user32" (ByVal hWnd As Long, ByVal hWndInsertAfter As Long, ByVal X As Long, ByVal Y As Long, ByVal cx As Long, ByVal cy As Long, ByVal wFlags As Long)
Private Sub Resize_Chrome()
Dim ChromeHandle As Long
ChromeHandle = FindWindow(vbNullString, "New Tab - Google Chrome")
SetWindowPos ChromeHandle, 0, 0, 0, 1000, 600, &H40
End Sub
This will:
Places the window at the top of the Z order.
Position in upper left corner (0,0)
With a 1000 pixels width by 600 pixels height (1000,600)
Activate the window (&H40)
Important notes:
The size is not changed if the window is maximized
This looks for the Chrome window not a specific tab.
The name of a Chrome window is the name of the active tab + " - Google Chrome".
I am printing barcodes and as part of the process I have a Chart object which has a textbox on it.
I render the barcode on it using the clsBarcode class I got from here
Generating Code 128 Barcodes using Excel VBA
Now the issue I have is that I can't tell the width of the barcode.
I generate the barcode on that chart object and then .export the chart as a jpeg file. I had been using a fixed size for the chart object but now I'm trying to print barcodes of different sizes and have to adjust the chart object to match the barcode size or else it gets clipped.
I found an strWidth function here
http://www.ozgrid.com/forum/showthread.php?t=94339
Unfortunately it uses a lookup table for commonly available fonts. There is no entry in the table for code128.fft.
So I am kind of stuck here. If I just resize my chart to be the long possible size of any barcode then I get a lot of wasted whitespace in my barcode image. And since I am printing these barcodes on 2"x4" stickers, you can guess space is at a premium.
I think the best course would be to populate the lookup table with values for code128 characters. The barcode class indicates that chr 32 to 126 and 200 to 211 are in use.
How can I figure out the mafChrWid(i) values for these chars ?
thanks !
For this function you need to name a cell BARCODE and set it's font code128.fft.
Function getBarCodeWidth(strBarcode As String) As Double
With Range("BARCODE")
.Formula = "=Code128_Str(" & strBarcode & ")"
.Worksheet.Columns(.Column).AutoFit
getBarCodeWidth = .Width
End With
End Function
I can't remember where I got the original code to determine font size. I modified it into an easy to use function that can be used to automatically resize a textbox to fit its contents. Drop the below code into its own module and you can then getLabelPixel(theControlYouWantToSizeToItsContents) as the textbox width.
Private Declare Function CreateDC Lib "gdi32.dll" Alias "CreateDCA" (ByVal lpDriverName As String, ByVal lpDeviceName As String, ByVal lpOutput As String, lpInitData As Long) As Long
Private Declare Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As Long, ByVal nWidth As Long, ByVal nHeight As Long) As Long
Private Declare Function CreateFontIndirect Lib "gdi32.dll" Alias "CreateFontIndirectA" (lpLogFont As LOGFONT) 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 GetTextExtentPoint32 Lib "gdi32.dll" Alias "GetTextExtentPoint32A" (ByVal hdc As Long, ByVal lpsz As String, ByVal cbString As Long, lpSize As SIZE) As Long
Private Declare Function MulDiv Lib "kernel32.dll" (ByVal nNumber As Long, ByVal nNumerator As Long, ByVal nDenominator As Long) As Long
Private Declare Function GetDC Lib "user32.dll" (ByVal hwnd As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32.dll" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function DeleteDC Lib "gdi32.dll" (ByVal hdc As Long) As Long
Private Const LOGPIXELSY As Long = 90
Private Type LOGFONT
lfHeight As Long
lfWidth As Long
lfEscapement As Long
lfOrientation As Long
lfWeight As Long
lfItalic As Byte
lfUnderline As Byte
lfStrikeOut As Byte
lfCharSet As Byte
lfOutPrecision As Byte
lfClipPrecision As Byte
lfQuality As Byte
lfPitchAndFamily As Byte
lfFaceName As String * 32
End Type
Private Type SIZE
cx As Long
cy As Long
End Type
Public Function getLabelPixel(textBox As Control) As Integer
Dim font As New StdFont
Dim sz As SIZE
font.Name = textBox.FontName
font.SIZE = textBox.FontSize
font.Weight = textBox.FontWeight
sz = GetLabelSize(textBox.Value, font)
getLabelPixel = sz.cx * 15 + 50 'Multiply this by 15 to get size in twips and +50 to account for padding for access form. .cx is width for font height us .cy
End Function
Private Function GetLabelSize(text As String, font As StdFont) As SIZE
Dim tempDC As Long
Dim tempBMP As Long
Dim f As Long
Dim lf As LOGFONT
Dim textSize As SIZE
' Create a device context and a bitmap that can be used to store a
' temporary font object
tempDC = CreateDC("DISPLAY", vbNullString, vbNullString, ByVal 0)
tempBMP = CreateCompatibleBitmap(tempDC, 1, 1)
' Assign the bitmap to the device context
DeleteObject SelectObject(tempDC, tempBMP)
' Set up the LOGFONT structure and create the font
lf.lfFaceName = font.Name & Chr$(0)
lf.lfHeight = -MulDiv(font.SIZE, GetDeviceCaps(GetDC(0), 90), 72)
'LOGPIXELSY
lf.lfItalic = font.Italic
lf.lfStrikeOut = font.Strikethrough
lf.lfUnderline = font.Underline
lf.lfWeight = font.Weight
'If font.Bold Then lf.lfWeight = 800 Else lf.lfWeight = 400
f = CreateFontIndirect(lf)
' Assign the font to the device context
DeleteObject SelectObject(tempDC, f)
' Measure the text, and return it into the textSize SIZE structure
GetTextExtentPoint32 tempDC, text, Len(text), textSize
' Clean up (very important to avoid memory leaks!)
DeleteObject f
DeleteObject tempBMP
DeleteDC tempDC
' Return the measurements
GetLabelSize = textSize
End Function
I'm looking for the fastest way to take a print-screen, and i found out that using Bitblt was my better choice, however, it only works for device context handle's, which means for me to retrieve a bitmap from that, i'd have to use multiple API's including CreateCompatibleBitmap, which in the end it probably takes the same time as using a managed way, like graphics.CopyFromScreen (which is a bit slow for me and also consumes alot of CPU, between 7-10% on a 2.3ghz quad-core processor...)
However, i still searched for a cleaner way of retrieving a bitmap from it, so i came up with this code:
<DllImport("user32.dll")> _
Public Shared Function GetDC(ByVal hWnd As IntPtr) As IntPtr
End Function
<DllImport("user32.dll")> _
Public Shared Function ReleaseDC(ByVal hWnd As IntPtr, ByVal hDC As IntPtr) As Integer
End Function
<DllImport("gdi32.dll")> _
Public Shared Function BitBlt(ByVal hdcDest As IntPtr, ByVal xDest As Integer, ByVal yDest As Integer, ByVal wDest As Integer, ByVal hDest As Integer, ByVal hdcSource As IntPtr, _
ByVal xSrc As Integer, ByVal ySrc As Integer, ByVal rop As TernaryRasterOperations) As Boolean
End Function
Dim hwNd As IntPtr = Nothing
hwNd = GetDC(GetDesktopWindow)
picHandle = GetDC(Me.PictureBox1.Handle)
BitBlt(picHandle, 0, 0, PictureBox1.Width, PictureBox1.Height, hwNd, 0, 0, TernaryRasterOperations.SRCCOPY)
ReleaseDC(hwNd, picHandle)
I can reach ~30 fps with this... But it has two problems as i said above:
Even if displaying it on a picturebox as i'm doing it above accomplished what i want, it doesn't resize to the picturebox control, even if i change those "0" values to the picturebox x and y coordinates.
I further searched and found there's a StretchBit API for that, and it does stretch, but it also reduces quality, (Even with the necessary call to SetStretchBltMode with parameter "HALFTONE" so it doesn't "corrupt" the pixels), it also reduces performance at least in 10+ fps...
But as i need to get it as bitmap object, with the other necessary API's for that, i ended up with almost half the performance (15~ fps) which is equivalent of graphics.CopyFromScreen.
So, i'm asking, is there another way to get a bitmap from the screen using Bitblt or similar without losing performance?
If there isn't a .Net way, i kindly ask for any language-way of doing that.
If you want raw performance, you will have to get away from managed code. This is easy enough using C++ with Visual Studio. You can make calls directly to the Windows API, bypassing the .NET runtime, managed code for your application, and the overhead of p/invokes in .NET.
If you are familiar with C#, you can take your C# code, convert it to C++ (which should be straightforward, with a lot of work to replace the CLI).
Private Declare Function BitBlt Lib "GDI32" ( _
ByVal hdcDest As Integer, _
ByVal nXDest As Integer, _
ByVal nYDest As Integer, _
ByVal nWidth As Integer, _
ByVal nHeight As Integer, _
ByVal hdcSrc As Integer, _
ByVal nXSrc As Integer, _
ByVal nYSrc As Integer, _
ByVal dwRop As System.Int32) As Boolean
Declare Function QueryPerformanceCounter Lib "Kernel32" (ByRef X As Long) As Short
Declare Function QueryPerformanceFrequency Lib "Kernel32" (ByRef X As Long) As Short
Const SRCCOPY As Integer = &HCC0020
Use a form with only a picturebox and a label in it. Set the anchors of picbox accordingly. In picbox down event:
Private Sub PictureBox1_MouseDown(sender As System.Object, e As System.Windows.Forms.MouseEventArgs) Handles PictureBox1.MouseDown
Dim Ctr1, Ctr2, Freq As Long
Dim dbl As Double
QueryPerformanceCounter(Ctr1)
Dim desktopDC As IntPtr = Nothing
Dim picboxDC As IntPtr = Nothing
desktopDC = GetDC(New IntPtr(0))
picboxDC = GetDC(PictureBox1.Handle)
BitBlt(picboxDC, 0, 0, PictureBox1.Width, PictureBox1.Height, desktopDC, 0, 0, SRCCOPY)
QueryPerformanceCounter(Ctr2)
QueryPerformanceFrequency(Freq)
dbl = (Ctr2 - Ctr1) / Freq
dbl *= 1000000
Label1.Text = dbl.ToString 'it is in microseconds
ReleaseDC(New IntPtr(0), desktopDC)
ReleaseDC(PictureBox1.Handle, picboxDC)
End Sub
Maximize your form and click in picturebox.
Edit: Fixed, I created a compatibleDC for the graphics object, and a handle for the bitmap (using b.gethbitmap), then used the SelectObject function inside GDI to select those two, and used the compatibleDC instead of hDc in the BitBlt function
I've been trying to draw a bitmap to the screen (device 0), however I have encountered a problem copying the graphics using BitBlt.
Initially, I was drawing directly to the desktop using SetPixel (gdi32), but it was slow, so now I am setting the pixels of a bitmap object and then creating graphics from that object, and copying the hdc of the graphics to the screen.
My guess is that I am adding the HDC of the graphics object to an intptr, which essentially gives me the HDC of the container of the graphics object, which is not what I need. However even so, I have not found any information on how I could copy a bitmap to a device other than using BitBlt.
This is my current code (Windows forms app, textbox, button) The textbox is the device to copy to, and the button starts it. For testing purposes, set the textbox text to 0, and press the button. You should see a black box (50x50px) in the top left corner of your screen. The colour should be blue if it is working correctly:
Public Class Form1
Declare Function GetDC Lib "user32.dll" (ByVal hwnd As Int32) As Int32
Declare Function ReleaseDC Lib "user32.dll" (ByVal hwnd As Int32, ByVal hdc As Int32) As Int32
Declare Function SetPixel Lib "gdi32.dll" (ByVal hdc As Integer, ByVal x As Integer, ByVal y As Integer, ByVal crColor As Integer) As Integer
Declare Function BitBlt Lib "gdi32.dll" (ByVal hdcDest As IntPtr, ByVal nXDest As Integer, ByVal nYDest As Integer, ByVal nWidth As Integer, ByVal nHeight As Integer, ByVal hdcSrc As IntPtr, ByVal nXSrc As Integer, ByVal nYSrc As Integer, ByVal dwRop As Int32) As Boolean
Declare Function CreateCompatibleBitmap Lib "gdi32.dll" (ByVal hdc As IntPtr, ByVal nWidth As Integer, ByVal nHeight As Integer) As IntPtr
Declare Function CreateCompatibleDC Lib "gdi32.dll" (ByVal hdc As IntPtr) As IntPtr
Dim x As Integer
Sub setpx(ByVal location As Point, ByVal color As Color)
b.SetPixel(location.X, location.Y, color)
End Sub
Sub drawrectangle(ByVal device As Integer, ByVal location As Point, ByVal size As Point, ByVal color As Color)
b = New Bitmap(size.X, size.Y)
For i = location.X To size.X - 1
For z = location.Y To size.Y - 1
setpx(New Point(i, z), color)
Next
Next
g = Graphics.FromImage(b)
Dim hDc As IntPtr = g.GetHdc
BitBlt(GetDC(device), location.X, location.Y, size.X, size.Y, hDc, location.X, location.Y, 13369376)
ReleaseDC(device, GetDC(device))
End Sub
Dim b As Bitmap
Dim g As Graphics
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Buttnon1.Click
Dim r As New Random
Dim timestart As Integer = Now.TimeOfDay.TotalMilliseconds
drawrectangle(TextBox1.Text, New Point(1, 1), New Point(50, 50), Color.Blue)
MsgBox(Now.TimeOfDay.TotalMilliseconds - timestart)
End Sub
End Class
The way it works is it calls the function setpx, given a location and color from within a loop iterating through all of the pixels in a box (50x50 in my code). The setpx function will then call the setpixel function on a bitmap b. This part is working fine.
Next, it will define a graphics object g from the bitmap, and I use BitBlt to copy g's hdc (g.gethdc) to the screen. This is not working correctly, is this the correct way of doing this?
You are passing Textbox1.text as a device descriptor, that won't work...You want textbox1.hwnd