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
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.
Hi I am new to VBA and this seems like a simple problem.
I am trying to get the dimensions of an image in excel and using the GetDeviceCaps function as a result.
I will get a variable not defined compile error regarding the VERTES parameter.
The code I use is the following:
Option Explicit
Private Type POINTAPI
x As Long
y As Long
End Type
Private Declare Function GetPixel Lib "gdi32" (ByVal hDC As Long, _
ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetCursorPos Lib "user32" _
(lpPoint As POINTAPI) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) _
As Long
Private Declare Function GetDeviceCaps Lib "gdi32" ( _
ByVal hDC As LongPtr _
, ByVal nIndex As Long _
) As LongPtr
Public Sub Test()
Dim tPOS As POINTAPI
Dim AColor As Long
Dim ADC As Long
Dim width As Integer
ADC = GetWindowDC(0)
width = GetDeviceCaps(ADC, VERTRES)
Call GetCursorPos(tPOS)
AColor = GetPixel(ADC, tPOS.x, tPOS.y)
ActiveWindow.Selection.ShapeRange(1).Fill.ForeColor.RGB = 14588691
Debug.Print "width"
Debug.Print width
End Sub
Questions appears to be so basic there isn't anything specific about this online.
There is a near exact question here
Read pixel colors of an image
The Op actually asks the same question as I'm asking for. But accepts an answer that is nearly there but not quite.. The code below (taken from that thread) does everything I need bar the pixel by pixel bit. If you click on an image it will give you the colour at the click site. As I want to scan the whole picture I though I'd just do an X Y scan and put the sequential X and Y's in instead of the returned X and Y of the GetCursorPos call. But how to get left position and width (for example) in pixels to start the scan? What would I put in my for next loop to address each pixel ?
So to clarify my question.
How to change the code below to scan every pixel of the image not just the clicked cursor position. Thanks
#If VBA7 Then
Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hdc As LongPtr, ByVal x As Long, ByVal y As Long) As Long
Private Declare PtrSafe Function GetCursorPos Lib "user32" (ByRef lpPoint As POINT) As LongPtr
Private Declare PtrSafe Function GetWindowDC Lib "user32" (ByVal hwnd As LongPtr) As LongPtr
#Else
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Private Declare Function GetCursorPos Lib "user32" (ByRef lpPoint As POINT) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
#End If
Private Type POINT
x As Long
y As Long
End Type
Sub Picture1_Click()
Dim pLocation As POINT
Dim lColour As Long
Dim lDC As Variant
lDC = GetWindowDC(0)
Call GetCursorPos(pLocation)
lColour = GetPixel(lDC, pLocation.x, pLocation.y)
Range("a1").Interior.Color = lColour
End Sub
Option Explicit
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hdc As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, ByVal nIndex As Long) As Long
Private Declare Function GetCurrentThreadId Lib "kernel32" () As Long
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Type RECT
Left As Long
Top As Long
Right As Long
Bottom As Long
End Type
Private Type POINTAPI
X As Long
Y As Long
End Type
Dim IDC As Long
Private Function ScreenDPI(bVert As Boolean) As Long
'*** Get screen DPI ***
Static lDPI&(1), lDC&
If lDPI(0) = 0 Then
lDC = GetDC(0)
lDPI(0) = GetDeviceCaps(lDC, 88&) 'horz
lDPI(1) = GetDeviceCaps(lDC, 90&) 'vert
lDC = ReleaseDC(0, lDC)
End If
ScreenDPI = lDPI(Abs(bVert))
End Function
Private Function PTtoPX(Points As Single, bVert As Boolean) As Long
'*** Swap Points to pixels ****
PTtoPX = Points * ScreenDPI(bVert) / 72
End Function
Sub GetImageRect(ByRef RC As RECT)
Dim RNG As Range
Set RNG = Sheet1.Range("A1")
'**** using the spread sheet cell A1 as a reference ***
'** find the details of th eimage and convert to pixels ***
Dim wnd As Window
Set wnd = RNG.Parent.Parent.Windows(1)
With Sheet1.Image1
RC.Left = PTtoPX(.Left * wnd.Zoom / 100, 0) + wnd.PointsToScreenPixelsX(0)
RC.Top = PTtoPX(.Top * wnd.Zoom / 100, 1) + wnd.PointsToScreenPixelsY(0)
RC.Right = PTtoPX(.Width * wnd.Zoom / 100, 0) + RC.Left
RC.Bottom = PTtoPX(.Height * wnd.Zoom / 100, 1) + RC.Top
End With
End Sub
Sub XYScanOfImage()
'*** put an active X image on sheet1 call it image1 and run this routine **
'** to get the colour information for each pixel *****
Dim RC As RECT
Dim ScanX As Single
Dim ScanY As Single
Dim ImX As Single
Dim ImY As Single
Dim PixCol As Single
Call GetImageRect(RC)
ImX = RC.Left
ImY = RC.Top
IDC = GetDC(0)
'*** scan image left to right top to bottom ****
For ScanX = RC.Left To RC.Right
For ScanY = RC.Top To RC.Bottom
PixCol = GetPixel(IDC, ScanX, ScanY)
'**** PUT CODE IN HERE TO PROCESS THE PIXEL COLOUR ****
Next
Next
IDC = ReleaseDC(0, IDC)
End Sub
The answer from perfo is fantastic - and works!
Some notes to help:
You have to add the image as an ActiveX control (go to the Excel Developer ribbon to to do this, then right-click on the image and set its name to Image1 - this should be the default anyway).
Also, make sure it's on a sheet called Sheet1 in VBA (again, this should be the default).
The routine returns an RGBA colour. I created the following routine to colour each cell:
Sub ColourCell(c As Range, ThisColour As Single)
'colour the passed in range
Dim Red As Byte
Dim Green As Byte
Dim Blue As Byte
Red = ThisColour And &HFF&
Green = (ThisColour And &HFF00&) / 256
Blue = (ThisColour And &HFF0000) / 65535
c.Interior.Color = RGB(Red, Green, Blue)
End Sub
I amended the answer to colour the cells from the active cell down and across as follows:
'*** scan image left to right top to bottom ****
Dim i As Integer
Dim j As Integer
Dim OriginalRowNumber As Integer
Dim OriginalColumnNumber As Integer
OriginalRowNumber = ActiveCell.Row
OriginalColumnNumber = ActiveCell.Column
i = OriginalRowNumber
j = OriginalColumnNumber
Sheet1.Select
Cells.EntireColumn.ColumnWidth = 0.63
Cells.EntireRow.RowHeight = 6
For ScanX = RC.Left To RC.Right
For ScanY = RC.Top To RC.Bottom
PixCol = GetPixel(IDC, ScanX, ScanY)
ColourCell Cells(j, i), PixCol
j = j + 1
Next
i = i + 1
If i Mod 5 = 0 Then Stop
j = OriginalColumnNumber
Next
There are a couple of extra bits in there to set a tiny grid size, so you can see your picture unfolding, and a debug line to pause the macro every 5 columns (it takes a LONG time to run).
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.
I created an userform in Powerpoint (2016) which allows the user to select some pictures with a filedialog. The filenames are stored in a listbox and the absolute paths in a collection. After an entry becomes selected, the picture should be displayed in the userform (like a preview). Therefore I use a frame control.
Because most of the pictures are png's, I use the GDI libraries to load the file as a bitmap and convert it to an IPicture object. Afterwards I just have to set the IPicture object to the Picture property of the control. These works fine and the picture is displayed correctly.
My problem is that the images have a large resolution (e.g. 1600x1200) and I want to resize the images. My first approach was to set the PictureSizeMode, but the PNG becomes unreadable. It seems that the PictureSizeModeZoom is distorting the image. The same effect happens if i use fmPicturesizeModeClip and reduce the zoom value.
After searching the web I found some posts about the render method (IPicture::Render (MSDN)). I used a spy program to determine the handle of my frame and then rendered the image with a lower resolution. Unfortunately the picture is still unreadable and looks the same as with the other approaches.
The images contain some measure plots with description labels and legends on it. At least the description header should be readable. If I change the size with paint and load the image afterwards to the userform it looks exactly like I want, but this is not a solution for me!
Is there any other way to reduce the size of an image with VBA and keep it readable?
Thanks for any help!
Update
Here are two png version of the linked picture i found on google. The first one has the size 1920x921 (reduced automatically from frame control), the second one 400x192 (reduced with paint). Both are loaded to a frame with the size 400x192.
The following code declares all libraries.
'#author Stephen Bullen, Rob Bovey
'#url http://www.rondebruin.nl/win/s2/win009.htm
'Declare a UDT to store a GUID for the IPicture OLE Interface
Private Type GUID
Data1 As Long
Data2 As Integer
Data3 As Integer
Data4(0 To 7) As Byte
End Type
#If VBA7 Then
'Declare a UDT to store the bitmap information
Private Type PICTDESC
Size As Long
Type As Long
hPic As LongPtr
hPal As LongPtr
End Type
'Declare a UDT to store the GDI+ Startup information
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As LongPtr
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
'Windows API calls into the GDI+ library
Private Declare PtrSafe Function GdiplusStartup Lib "GDIPlus" (token As LongPtr, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As LongPtr = 0) As Long
Private Declare PtrSafe Function GdipCreateBitmapFromFile Lib "GDIPlus" (ByVal filename As LongPtr, bitmap As LongPtr) As Long
Private Declare PtrSafe Function GdipCreateHBITMAPFromBitmap Lib "GDIPlus" (ByVal bitmap As LongPtr, hbmReturn As LongPtr, ByVal background As LongPtr) As Long
Private Declare PtrSafe Function GdipDisposeImage Lib "GDIPlus" (ByVal image As LongPtr) As Long
Private Declare PtrSafe Sub GdiplusShutdown Lib "GDIPlus" (ByVal token As LongPtr)
Private Declare PtrSafe Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As PICTDESC, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
#Else
'Declare a UDT to store the bitmap information
Private Type PICTDESC
Size As Long
Type As Long
hPic As Long
hPal As Long
End Type
'Declare a UDT to store the GDI+ Startup information
Private Type GdiplusStartupInput
GdiplusVersion As Long
DebugEventCallback As Long
SuppressBackgroundThread As Long
SuppressExternalCodecs As Long
End Type
'Windows API calls into the GDI+ library
Private Declare Function GdiplusStartup Lib "GDIPlus" (token As Long, inputbuf As GdiplusStartupInput, Optional ByVal outputbuf As Long = 0) As Long
Private Declare Function GdipCreateBitmapFromFile Lib "GDIPlus" (ByVal filename As Long, bitmap As Long) As Long
Private Declare Function GdipCreateHBITMAPFromBitmap Lib "GDIPlus" (ByVal bitmap As Long, hbmReturn As Long, ByVal background As Long) As Long
Private Declare Function GdipDisposeImage Lib "GDIPlus" (ByVal image As Long) As Long
Private Declare Sub GdiplusShutdown Lib "GDIPlus" (ByVal token As Long)
Private Declare Function OleCreatePictureIndirect Lib "oleaut32" (PicDesc As PICTDESC, RefIID As GUID, ByVal fPictureOwnsHandle As Long, IPic As IPicture) As Long
#End If
And here the methods:
'#brief Showing the image with the given path
Private Sub showImage(ByVal path As String)
LoadPictureGDI Frame1, path
End Sub
' Procedure: LoadPictureGDI
' Purpose: Loads an image using GDI+
' Returns: The image as an IPicture Object
Public Sub LoadPictureGDI(ByVal c As Object, ByVal sFilename As String)
Dim uGdiInput As GdiplusStartupInput
Dim lResult As Long
#If VBA7 Then
Dim hGdiPlus As LongPtr
Dim hGdiImage As LongPtr
Dim hBitmap As LongPtr
#Else
Dim hGdiPlus As Long
Dim hGdiImage As Long
Dim hBitmap As Long
#End If
'Initialize GDI+
uGdiInput.GdiplusVersion = 1
lResult = GdiplusStartup(hGdiPlus, uGdiInput)
If lResult = 0 Then
'Load the image
lResult = GdipCreateBitmapFromFile(StrPtr(sFilename), hGdiImage)
If lResult = 0 Then
'Create a bitmap handle from the GDI image
lResult = GdipCreateHBITMAPFromBitmap(hGdiImage, hBitmap, 0)
'Create the IPicture object from the bitmap handle
'and show it in the frame.
Set c.Picture = CreateIPicture(hBitmap)
'Tidy up
GdipDisposeImage hGdiImage
End If
'Shutdown GDI+
GdiplusShutdown hGdiPlus
End If
End Sub
' Procedure: CreateIPicture
' Purpose: Converts a image handle into an IPicture object.
' Returns: The IPicture object
#If VBA7 Then
Private Function CreateIPicture(ByVal hPic As LongPtr) As IPicture
#Else
Private Function CreateIPicture(ByVal hPic As Long) As IPicture
#End If
Dim lResult As Long
Dim uPicInfo As PICTDESC
Dim IID_IDispatch As GUID
Dim IPic As IPicture
'OLE Picture types
Const PICTYPE_BITMAP = 1
' Create the Interface GUID (for the IPicture interface)
With IID_IDispatch
.Data1 = &H7BF80980
.Data2 = &HBF32
.Data3 = &H101A
.Data4(0) = &H8B
.Data4(1) = &HBB
.Data4(2) = &H0
.Data4(3) = &HAA
.Data4(4) = &H0
.Data4(5) = &H30
.Data4(6) = &HC
.Data4(7) = &HAB
End With
' Fill uPicInfo with necessary parts.
With uPicInfo
.Size = Len(uPicInfo)
.Type = PICTYPE_BITMAP
.hPic = hPic
.hPal = 0
End With
' Create the Picture object.
lResult = OleCreatePictureIndirect(uPicInfo, IID_IDispatch, True, IPic)
' Return the new Picture object.
Set CreateIPicture = IPic
End Function
From your examples, it appears that the form is doing a simple downsize of the image (essentially tossing out pixels) where Paint is downsampling, will tend to make the image a bit softer looking but will do a better job of preserving fine detail in cases like your images.
You might be able to use an image processing library like FreeImage to downsample the original images for use on your form.