I am currently using a 4K (3840x2160) 28-inch (631.93 mm x 359.78 mm) 60Hz IPS monitor, which according to the manufacturer the pixel per inch (DPI/PPI) value should be 157.35.
However, when I use the GetDeviceCaps function, it returns 144. As I am not very familiar with this topic I would be extremely grateful if someone can explain from where the difference is coming. Last but not least, is there a way to calculate my PPI correctly?
Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As Long) As Long
Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal Index As Long) As Long
Public Function returnDPI()
Dim hDC As Long
hDC = GetDC(0)
MsgBox GetDeviceCaps(hDC, 88)
MsgBox GetDeviceCaps(hDC, 90)
End Function
TLDR: you're not measuring what you think you're measuring;
88 and 90 are logical pixels per inch (see this enum and the docs for GetDeviceCaps):
https://www.pinvoke.net/default.aspx/gdi32.getdevicecaps
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getdevicecaps
Logical pixels per inch isn't the same as how many pixels per inch your monitor has. A point (eg 12 pt font), is 1/72 of an inch (in the real world). That's just the physical definition of it. So as far as your computer is concerned, whatever size a 72 pt font is on the screen is an inch (a logical inch). That's slightly compounded by your resolution settings. Since fonts (or anything) don't display the same size on different monitors, you can set your device to a scaling factor, DPI. This is D(ots) P(er) I(nch), but in this case the inch is a logical inch. So if you have your DPI set to 144, then the computer uses 144 dots per logical inch and that's what you're going to get back from LOGPIXELSX and LOGPIXELSY.
https://learn.microsoft.com/en-us/windows/win32/learnwin32/dpi-and-device-independent-pixels
IF you want to figure out your PPI, then you can use this calculation:
https://www.calculatorsoup.com/calculators/technology/ppi-calculator.php
The manufacturer has told you the diagonal is 28", but you can check that with GetDeviceCaps, using HORZSIZE (4) and VERTSIZE (6). This will be in mm, so to convert to inches you divide by 25.4. Once you have that, you can get the diagonal with the pythagorean theorem. From there, you can get the resolution of the screen in pixels with HORZRES (8) and VERTRES (10), then use the pythagorean theorem again to get the diagonal in pixels.
All that's left is to divide the diagonal in pixels by the diagonal in inches.
Code:
Declare PtrSafe Function GetDC Lib "user32" (ByVal hWnd As LongPtr) As LongPtr
Declare PtrSafe Function GetDeviceCaps Lib "gdi32" (ByVal hDC As LongPtr, ByVal Index As Long) As Long
Option Explicit
Public Function returnPPI() As Double
Dim hDC As LongPtr
Dim h As Double, v As Double, di As Double, dpx As Double, ppi As Double
Dim x As Long, y As Long
hDC = GetDC(0)
h = GetDeviceCaps(hDC, 4) / 25.4 'HORSIZE in mm converted to inches
v = GetDeviceCaps(hDC, 6) / 25.4 'VERTSIZE in mm converted to inches
di = (h ^ 2 + v ^ 2) ^ 0.5 ' diagonal in inches, using Pythagoras
x = GetDeviceCaps(hDC, 8) 'HORZRES in pixels
y = GetDeviceCaps(hDC, 10) 'VERTRES in pixels
dpx = (x ^ 2 + y ^ 2) ^ 0.5 ' diagonal in pixels, using Pythagoras
ppi = dpx / di
Dim this As Worksheet: Set this = ActiveSheet
this.Cells(1, 1) = "Screen Height, inches"
this.Cells(1, 2) = v
this.Cells(2, 1) = "Screen Width, inches"
this.Cells(2, 2) = h
this.Cells(3, 1) = "Screen Diagonal, inches"
this.Cells(3, 2) = di
this.Cells(5, 1) = "Screen Height, pixels"
this.Cells(5, 2) = y
this.Cells(6, 1) = "Screen Width, pixels"
this.Cells(6, 2) = x
this.Cells(7, 1) = "Screen Diagonal, pixels"
this.Cells(7, 2) = dpx
this.Cells(9, 1) = "PPI"
this.Cells(9, 2) = ppi
returnPPI = ppi
End Function
Be aware that these LongPtr types will error on VBA versions before 7 and you would need to do conditional compilation. I didn't include that because this should work on 2010+, and there are plenty of resources out there already for supporting older versions.
Related
I am trying to find where the color differs and then crop that part of the image out of it.
Is there any method to get a pixel by pixel image color?
I don't think I can based off of these methods and because it is a bitmap image.
I know there is a set transparent method but the problem is I need to set the widths or heights of logos to be the same height/width and the transparent function doesn't change the size of the picture
If a method does this I would write a function that finds where the image changes from white and then crop from the top middle and bottom.
Example image
As far as I can tell it is impossible to do so within Powerpoint, however there might be a way using outside conditions such as by opening an excel project and using the code from this post VBA Get Colour of Pixel. I'm sorry that I cannot help you in any other way. My main suggestion would be to use normal numbers to crop it. Perhaps if you found a specific thing between the images that was different (Such as one type of image's height was different than the other type of image so you could use that to know how far to crop the image.), however as far as I can tell, No. There is no way to do this with powerpoint unless they added a feature.
I was recently working on a similar problem, and I developed a VBA solution for automatically cropping a white border from an image
In that answer, I am only looking at one side of the image because I assume that the border is uniform all of the way around, so it would not directly work for this case. Also, that example only modifies the slide, and it does not produce an output file
In the below example, each side of the image is examined and cropped according to how much whitespace is found. The resultant slide is then exported back to the original directory with the name croppedFile.
Private Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal Y As Long) As Long
Private Function PixelTest(objPict As Object, ByVal X As Long, ByVal Y As Long) As Long
Dim lDC As Variant
lDC = CreateCompatibleDC(0)
SelectObject lDC, objPict.Handle
PixelTest = GetPixel(lDC, X, Y)
DeleteDC lDC
End Function
Sub AutoCropper()
Dim myDocument As Slide, fileSystem As Object, fileFolder As Object
Dim fileItem As Object, objPict As Object, objImage As Object
Dim i As Integer, startingPoint As Integer, endingPoint As Integer
Dim MidPoint As Integer, filePath As String, fileName As String
Dim cropScale As Single, margin As Single, reverseScan As Integer
Dim importHeight As Single, importWidth As Single, resolutionScale As Integer
Dim xlocation As Single, yLocation As Single
Dim restoreLayout As Boolean
filePath = "D:\Pictures"
fileName = "Example.bmp"
Set fileSystem = CreateObject("Scripting.FileSystemObject")
Set fileFolder = fileSystem.GetFolder(filePath)
Set objImage = CreateObject("WIA.ImageFile")
cropScale = 3.4
resolutionScale = 10
importWidth = 330
importHeight = 250
xlocation = 390
yLocation = 200
For Each fileItem In fileFolder.Files
If fileItem.Name = fileName Then
i = i + 1
On Error GoTo insertSlide
Set myDocument = ActivePresentation.Slides(i)
If myDocument.CustomLayout.Name = "Picture with Caption" Then
myDocument.Layout = ppLayoutText
restoreLayout = True
End If
Set preCroppedPic = myDocument.Shapes.AddPicture(fileName:=fileFolder & "\" & _
fileItem.Name, LinkToFile:=msoTrue, SaveWithDocument:=msoTrue, _
Left:=xlocation, Top:=yLocation, Width:=importWidth, Height:=importHeight)
preCroppedPic.Export filePath & "\Temp.bmp", ppShapeFormatBMP, preCroppedPic.Width, preCroppedPic.Height, ppScaleToFit
Set objImage = CreateObject("WIA.ImageFile")
objImage.LoadFile filePath & "\Temp.bmp"
Set objPict = LoadPicture(filePath & "\Temp.BMP")
endingPoint = objImage.Width
MidPoint = (0.5 * objImage.Height)
For marginScan = 1 To endingPoint
On Error Resume Next
If Not (PixelTest(objPict, marginScan, MidPoint) Like "1677*") Then
margin = marginScan * cropScale
preCroppedPic.PictureFormat.CropLeft = margin
Exit For
End If
Next
For marginScan = 1 To endingPoint
reverseScan = endingPoint - marginScan
If Not (PixelTest(objPict, reverseScan, MidPoint) Like "1677*") Then
margin = marginScan * cropScale
preCroppedPic.PictureFormat.CropRight = margin
Exit For
End If
Next
endingPoint = objImage.Height
MidPoint = (0.5 * objImage.Width)
For marginScan = 1 To endingPoint
If Not (PixelTest(objPict, MidPoint, marginScan) Like "1677*") Then
margin = marginScan * cropScale
preCroppedPic.PictureFormat.CropTop = margin
Exit For
End If
Next
For marginScan = 1 To endingPoint
reverseScan = endingPoint - marginScan
If Not (PixelTest(objPict, MidPoint, reverseScan) Like "1677*") Then
margin = marginScan * cropScale
preCroppedPic.PictureFormat.CropBottom = margin
' finalHeight = finalHeight - margin
Exit For
End If
Next
If restoreLayout Then
myDocument.Layout = ppLayoutPictureWithCaption
restoreLayout = False
End If
preCroppedPic.Export filePath & "\CroppedImage.bmp", ppShapeFormatBMP, (resolutionScale * importWidth), (resolutionScale * importHeight), ppScaleToFit
Exit For
End If
Next fileItem
Exit Sub
insertSlide:
Set myDocument = ActivePresentation.Slides.Add(i, ppLayoutText)
Resume Next
End Sub
The preceding code produces the following result with the precropped image on the left and the postcropped image on the right:
Obviously, a correct file path and file name have to be supplied, but here are a few not so obvious things that need to be taken into consideration when working with this script:
• This program has been tested and confirmed to work for BMP, JPEG, GIF, and PNG files, but the file name and extension are case sensitive, so if you run the code and nothing happens, I would check that first.
• I've tested this on multiple systems, and I've found that the pixel analyzer in this program will not work for PNGs. To make this script PNG compatible, because that is what I normally work with, I had to perform an intermediary file conversion that creates a temporary BMP file. Between the different environments I tested this script on, I found that the export size varied quite a bit, but it was always proportional to the original picture. For this reason, there is a cropScale value that can be used to account for this ratio. A cropScale value of 2 for example will crop 2 white pixels from the original photo for every 1 pixel detected in the temporary bmp.
• Also, due to the export descrepencies, I've found that the export file will often have a lower resolution than the original, so a resolutionScale parameter has been added to compensate for this in the output file. This along with the cropScale parameters should make it simple to get this script working on wide variety of systems.
• Finally, this script can be modified to crop any border color or even gradient colors by modifying the If Not Like parameters in the four cropping loops, but the colors returned from the pixel analyzer are not RGB values. Rather they are Long values.
• A list of Long color values can be found here: Long Color Chart
• For more information on scaling picture sizes and resolutions in PowerPoint, I found this excellent resource while researching this solution:Unravelling PowerPoint picture size and resolution
I've been asked to code the ability to click on an image in Excel and add a shape on top of it (it's a body diagram for a physiotherapist, the shape will indicate the site of the patient's pain). My code does this OK by using the mouse down event of an ActiveX image control:
Private Sub bodypic_MouseDown(ByVal Button As Integer, _
ByVal Shift As Integer, ByVal x As Single, ByVal y As Single)
ClickShape x, y
End Sub
Sub ClickShape(x As Single, y As Single)
Dim shp As Shape
Dim cursor As Point
Set shp = ActiveSheet.Shapes.AddShape(msoShapeMathMultiply, x + ActiveSheet.Shapes("bodypic").Left, _
y + ActiveSheet.Shapes("bodypic").Top, 26, 26)
With shp.Fill
.ForeColor.RGB = RGB(255, 0, 0)
.BackColor.RGB = RGB(255, 0, 0)
End With
shp.Line.Visible = False
End Sub
The problem is that while the mouse cursor is over the diagram the shape is not visible. Only when the mouse is moved off of the diagram does the shape appear.
I've tried various methods to refresh the screen, selecting a cell, even changing the cursor position via the SetCursor method in Lib user32. Nothing seems to work except for the user actually moving the mouse.
To recreate the issue: insert an ActiveX image control roughly 200 x 500 px, add a jpeg image to the control, add the mouse down code to the worksheet and the click shape code to a module.
This is very hacky but I discovered that hiding and unhiding the image solves the problem:
ActiveSheet.Shapes("bodypic").Visible = False
ActiveSheet.Shapes("bodypic").Visible = True
End Sub
I'd welcome more elegant answers!
I have a limited amount of success with this code:-
Option Explicit
Private Type POINTAPI
x As Long
y As Long
End Type
Private Declare Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long
Private Declare Function SetCursorPos Lib "user32" (ByVal x As Integer, ByVal y As Integer) As Integer
Sub ClickShape(ByVal x As Single, ByVal y As Single)
Dim Shp As Shape
Dim Pos As POINTAPI
GetCursorPos Pos
SetCursorPos Pos.x + 300, Pos.y
With ActiveSheet
With .Shapes("bodypic")
x = x + .Left
y = y + .Top
End With
Set Shp = .Shapes.AddShape(msoShapeMathMultiply, x, y, 26, 26)
End With
With Shp
.Name = "Mark1"
.Line.Visible = False
With .Fill
.ForeColor.RGB = RGB(255, 0, 0)
.BackColor.RGB = RGB(255, 0, 0)
End With
End With
End Sub
In essence, what it does is to move the cursor out of the image. Then it takes about a second for the mark to appear. The delay will be longer the more marks there are. Note that my movement of 300 pixels is random. You would have to work out where to move it, so long as it is outside the image. I tried moving it back immediately, but that didn't work, and timing the return would be tricky because of the variations in the delay.
I experimented with another concept where I created the mark first and made it invisible. Then, on MouseUp (MouseUp is the more suitable event), I moved the mark and made it visible. That was faster, but it limits you to a single mark or condemns you to a lot of name management. Giving a name to the mark is a leftover from that experiment. Actually, it looked quite nice since I could move the mark by repeatedly clicking on different positions. If you need only one mark I recommend to pursue that idea.
If you need several marks, another leftover from my experiments is the idea to add a feature to delete (or hide) a mark, perhaps on double-click.
I am trying to find formula for transforming X and Y coordinates of MouseDown/MouseUp event of Chart to Axes's coordinate system, i.e to a pair {Category;Value}. Excel Object Model Reference for Chart.MouseDown event says:
The X coordinate of the mouse pointer in chart object client coordinates.
I've found useful methods and properties for the task:
Axis: Left, Width
MouseDown/MouseUp: x, y
ChartObject: Left, Width
Application: CentimetersToPoints, InchesToPoints, MeasurementUnit
Window: ActivePane, PointsToScreenPixelsX, PointsToScreenPixelsY
Pane: PointsToScreenPixelsX, PointsToScreenPixelsY
I've also found that X an Y values of a "mouse" event are in pixels -- see test:
Private Sub m_target_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long)
Dim v_l As Long, v_r As Long
'X and Y are in pixels!
With m_target
v_l = ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left)
v_r = ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left + .Parent.Width)
Debug.Print "X="; x; "Y="; y; "Xc="; x / 12 * 9; "Yc="; y / 12 * 9; VBA.chr$(13); _
"xlCategory: Left="; .Axes(xlCategory).Left; " Top="; .Axes(xlCategory).Top; " Width="; .Axes(xlCategory).Width; VBA.chr$(13); _
"xlValue: Left="; .Axes(xlValue).Left; " Top="; .Axes(xlValue).Top; " Width="; .Axes(xlValue).Width; VBA.chr$(13); _
"PlotArea: Left="; .PlotArea.Left; " Top="; .PlotArea.Top; " Width="; .PlotArea.Width; " Height="; .PlotArea.Height; VBA.chr$(13); _
"PlotArea: InsideLeft="; .PlotArea.InsideLeft; " InsideTop="; .PlotArea.InsideTop; " InsideWidth="; .PlotArea.InsideWidth; " InsideHeight="; .PlotArea.InsideHeight; VBA.chr$(13); _
"ChartObject: Left="; .Parent.Left; " Top="; .Parent.Top; ", Width="; .Parent.Width; " Height="; .Parent.Height; VBA.chr$(13); _
"ActiveWindow: Caption="; ActiveWindow.Caption; " Left="; ActiveWindow.Left; " UsableWidth="; ActiveWindow.UsableWidth; " Width="; ActiveWindow.Width; VBA.chr$(13); _
"ChartObject: Left_px="; ActiveWindow.ActivePane.PointsToScreenPixelsX(.Parent.Left); " Width_px="; v_r - v_l
End With
End Sub
The ratio 9/12 was found in "Excel VBA: Why is so big difference between values returned by PointsToScreenPixelsX for Window and for Pane?"
Clicking right border of a chart gives the following output:
X= 1838 Y= 220 Xc= 1378.5 Yc= 165
xlCategory: Left= 56 Top= 186 Width= 1286
xlValue: Left= 35 Top= 2 Width= 21
PlotArea: Left= 34.7092125984252 Top=-4 Width= 1320.84480314961 Height= 210
PlotArea: InsideLeft= 55.6250393700787 InsideTop= 2.10251968503937 InsideWidth= 1286.32645669291 InsideHeight= 183.845826771654
ChartObject: Left= 132.374954223633 Top= 0 , Width= 1377.72351074219 Height= 210
ActiveWindow: Caption=Q99708-VSSPVFGATSSIK-DC-1147-DC-r0001-dn.xlsx Left=-2 UsableWidth= 1279.5 Width= 1298.25
ChartObject: Left_px=-310 Width_px= 1837
Width_px is close to X and calculated as v_r - v_l using converted left (v_l) and right (v_r) border to pixels by ActiveWindow.ActivePane.PointsToScreenPixelsX.
But how to find exact formula to get {Category;Value} from {X;Y} which should work for any resolution (pixels per inch), MeasurementUnit, Pane.Left/Width, Chart.Left/Width, PlotArea.Left/Width and Axis.Left/Width?
I've found the formula. The following surprises considerably complicated this:
ChartArea.Width <> ChartObject.Width and ChartArea.Height <> ChartObject.Height!
It is possible to move a shape in chart coordinate system out of positive coordinates.
I've created a line shape in a chart and moved line to the left constraint of the chart. And line’s left was -4! Then I've tried to set the Shape.Left property to -4. This does not work: its value becomes 0 immediately after assignment. But the Shape.IncrementLeft method successfully moved line to the left constraint of a chart (-4). Similarly the Shape.IncrementTop method changes a Shape.Top property value. As a result a transformation from pixels to points was found.
Then with experiments I've found that PloatArea.Inside<Left,Top,Width,Height> must be used instead of corresponding properties of Axis to transform {X;Y} pair in points to a {Category;Value} pair.
First, find shift of ChartArea object in a ChartObject using movement of line shape to left and top constraints:
Sub extract_transform(ByVal p_chart As Chart, ByRef p_sh_x As Double, ByRef p_sh_y As Double)
Dim v_ScreenUpdating As Boolean, v_sh As Double
With p_chart.Shapes.AddShape(msoLine, 0, 0, 20, 0)
p_sh_x = 0: .IncrementLeft -10
While p_sh_x <> .Left
p_sh_x = .Left
.IncrementLeft -10 'Move to the left constaint
Wend
p_sh_y = 0: .IncrementTop -10
While p_sh_y <> .Top
p_sh_y = .Top
.IncrementTop -10 'Move to the top constaint
Wend
.delete 'Collecting garbage
End With
End Sub
Using found {left;top} of ChartArea object in Chart coordinate system a transformation of points to pixels is performed as follows:
Sub pixels2points(ByVal p_pane As Pane, ByVal x_pi As Long, ByVal y_pi As Long, ByRef x_pt As Double, ByRef y_pt As Double)
Dim v_sc As Double
With p_pane
v_sc = (.PointsToScreenPixelsX(1000) - .PointsToScreenPixelsX(0)) / 1000
x_pt = m_sh_x + x_pi / v_sc
v_sc = (.PointsToScreenPixelsY(1000) - .PointsToScreenPixelsY(0)) / 1000
y_pt = m_sh_y + y_pi / v_sc
End With
End Sub
The pixels2points assumes x_pi and y_pi are in the chart coordinate system in pixels.
m_sh_x and m_sh_y are of type Double and are members of a class c_report.
p_pane is a key object: only Pane.PointsToScreenPixels<X,Y> methods transform points to pixels and it depends on scaling factor of a window. Also the same sheet may appear in many windows, hence the corresponding Pane object must be used.
To transform {X;Y} pair in points to a {Category;Value} pair a chart wrapper class has the following method:
Sub pt2axes(ByVal p_axes As Axes, ByVal p_pa As PlotArea, ByVal x_pt As Double, ByVal y_pt As Double, ByRef p_cat As Double, ByRef p_val As Double)
With p_axes(xlCategory)
p_cat = (x_pt - p_pa.InsideLeft) / p_pa.InsideWidth * (.MaximumScale - .MinimumScale) + .MinimumScale
End With
With p_axes(xlValue)
p_val = ((p_pa.InsideTop + p_pa.InsideHeight) - y_pt) / p_pa.InsideHeight * (.MaximumScale - .MinimumScale) + .MinimumScale
End With
End Sub
Transformation formula for Y assumes that the origin of coordinates is at the bottom of a chart.
And now the application of these methods in handling the Chart.MouseDown event:
Private Sub m_target_MouseDown(ByVal Button As Long, ByVal Shift As Long, ByVal x As Long, ByVal y As Long)
Dim v_x As Double, v_y As Double, v_cat As Double, v_val as Double
With m_target
Debug.Print "X="; x; "Y="; y
m_report.pixels2points ActiveWindow.ActivePane, x, y, v_x, v_y
pt2axes .Axes, .PlotArea, v_x, v_y, v_cat, v_val
Debug.Print "cat="; v_cat; "val="; v_val
End With
End Sub
m_target class member is of type Chart. m_report class member is of type c_report and serves as a reference to a parent object which has the pixels2points method.
To test formulas a wide chart, frozen panes and scrolls at scale 400% were used and points at crossing grid lines with visible coordinates {270;2} and {10;6} were clicked. As a result the output of the Immediate window is:
X= 6980 Y= 849
cat= 270.030767164932 val= 2.00522623783961
X= 564 Y= 522
cat= 9.97421024128619 val= 6.00822155418118
I suppose the difference +/-0.03 for Category axis occurred because of pixels are discrete and the vertical grid line step had not integer value in pixels.
Below is some code I created starting with code from bettersolutions.com
It is an approach I just developed today using .GetChartElement to identify the pixel coordinates where the horizontal and vertical axes are located. Once those are found, the rest is fairly straightforward. In my testing it is accurate, on both chart sheets and embedded charts, and does in fact find the pixels coincident with the axes. The key is to work from the inside out to find each axis.
First code block goes in a Class module named EventClass.
Second code block goes in a standard module.
Public WithEvents ExcelChartEvents As Excel.Chart
Private Sub ExcelChartEvents_MouseMove( _
ByVal Button As Long, _
ByVal Shift As Long, _
ByVal x As Long, _
ByVal y As Long)
Dim oAxis As Excel.Axis
Dim chtX As Double
Dim chtY As Double
Dim pixAtVertAxis As Long, pixAtHorzAxis As Long
Dim pts2pix#, pix2pts#, pts2chtX#, pts2chtY#
pix2pts = PointsPerPixel() / (ActiveWindow.Zoom / 100)
pts2pix = 1 / pix2pts
GetPixAtHorizontalAndVerticalAxes pixAtVertAxis, pixAtHorzAxis
Set oAxis = ActiveChart.Axes(xlCategory)
pts2chtX = (oAxis.MaximumScale - oAxis.MinimumScale) / ActiveChart.PlotArea.InsideWidth
chtX = oAxis.MinimumScale + (x - pixAtVertAxis) * pix2pts * pts2chtX
Set oAxis = ActiveChart.Axes(xlValue)
pts2chtY = (oAxis.MaximumScale - oAxis.MinimumScale) / ActiveChart.PlotArea.InsideHeight
chtY = oAxis.MinimumScale - (y - pixAtHorzAxis) * pix2pts * pts2chtY
Application.StatusBar = "(" & Format(chtX, "#.000") & " , " & Format(chtY, "#.000") & ")"
If (Shift = 1) Then
'do this when shift key is held down
Dim chtID&, arg1&, arg2&
ActiveChart.GetChartElement x, y, chtID, arg1, arg2
Application.StatusBar = "(pxX=" & Format(x, "000") & " , pxY=" & Format(y, "000") & ")" _
& " (chtX=" & Format(chtX, "#.000") & " , chtY=" & Format(chtY, "#.000") & ")" _
& " chartID=" & chtID
End If
End Sub
second code block:
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function GetDeviceCaps Lib "gdi32" (ByVal hDC As Long, ByVal nIndex As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long
Private Const LOGPIXELSX = 88 'Pixels/inch in X
'A point is defined as 1/72 inches
Private Const POINTS_PER_INCH As Long = 72
Public oChart As EventClass
Public Sub StartCursorReadout()
'run this to start readout of the mouse location in chart coordinates
If ActiveChart Is Nothing Then MsgBox "First select a Chart or go to a ChartSheet.": Exit Sub
Set oChart = Nothing
Set oChart = New EventClass
Set oChart.ExcelChartEvents = ActiveChart
End Sub
Public Function PointsPerPixel() As Double
'Return the number of Excel points equal to one pixels. Usually this is 0.6
Dim hDC As Long
Dim lDotsPerInch As Long
hDC = GetDC(0)
lDotsPerInch = GetDeviceCaps(hDC, LOGPIXELSX)
PointsPerPixel = POINTS_PER_INCH / lDotsPerInch
ReleaseDC 0, hDC
End Function
Public Sub GetPixAtHorizontalAndVerticalAxes(ByRef pixAtVertAxis As Long, ByRef pixAtHorzAxis As Long)
Dim x&, y&, chtID&, arg1&, arg2&, lastchtID&
Dim pts2pix As Double, success As Boolean
pts2pix = 1 / (PointsPerPixel() / (ActiveWindow.Zoom / 100))
With ActiveChart
success = False: lastchtID = 0
For y = Int((.PlotArea.InsideTop + .PlotArea.InsideHeight) * pts2pix) - 20 To Int((.PlotArea.InsideTop + .PlotArea.InsideHeight) * pts2pix) - 200 Step -10
For x = Int(.PlotArea.InsideLeft * pts2pix) + 20 To Int(.PlotArea.InsideLeft * pts2pix) Step -1
.GetChartElement x, y, chtID, arg1, arg2
If chtID = xlAxis Then success = True: Exit For
If chtID = xlChartArea And lastchtID = xlPlotArea Then
success = True: x = x + 1: Exit For
End If
lastchtID = chtID
Next x
If success Then pixAtVertAxis = x: Exit For
Next y
success = False: lastchtID = 0
For x = Int(.PlotArea.InsideLeft * pts2pix) + 50 To Int(.PlotArea.InsideLeft * pts2pix) + 200 Step 10
lastchtID = 0
For y = Int((.PlotArea.InsideTop + .PlotArea.InsideHeight) * pts2pix) - 20 To Int((.PlotArea.InsideTop + .PlotArea.InsideHeight) * pts2pix) + 20 Step 1
.GetChartElement x, y, chtID, arg1, arg2
If chtID = xlAxis Then success = True: Exit For
If chtID = xlChartArea And lastchtID = xlPlotArea Then
success = True: y = y - 1: Exit For
End If
lastchtID = chtID
Next y
If success Then pixAtHorzAxis = y: Exit For
Next x
End With
End Sub
I have capture signatures using Msink Obj control from my form and saved them in signature field in my SQL database and datatype for it is image.
I have converted the saved sign data in to .gif and also shown it in image control in my form but now I want to calculate number of black pixels in this image or count of it.
So please suggest how can I go with it?
I have found an windows API for it which can we used for getting pixel count.
It is GetPixel Lib "gdi32" API below is sample code for it :-
Option Compare Database
'Following Two API'S Are Added To Count The Pixles
Private Declare Function GetPixel Lib "gdi32" (ByVal hdc As Long, ByVal X As Long, ByVal y As Long) As Long
Private Declare Function GetWindowDC Lib "user32" (ByVal hwnd As Long) As Long
Public Function GetPixlesTest(pHwnd As Long) As Integer: On Error Resume Next
Dim i As Long, j As Long, nBlack As Long, nWhite As Long, pHdc As Long, tempColor As Long
Dim count As Integer
count = 0 'Initializing count with 0
With Forms!frmTestSign!imgTest 'Getting Form Image Control
pHdc1 = GetWindowDC(pHwnd)
For i = 0 To .Width
For j = 0 To .Height`enter code here`
tempColor = GetPixel(pHdc1, i, j)
If tempColor = vbBlack Then 'Counting for black pixles only.
nBlack = nBlack + 1
End If
Next
Next
End With
TotalBlack = nBlack - 611 'Substracting 611 becuase it has default 611 black pixle on my form
GetPixlesPractical = TotalBlack
End Function
Calling above function :-
gstrpixlecount = GetPixlesPractical(Me.hwnd) 'Call it on same form in which you have image control to get pixles.
Plesae follow below link for more info :-
http://msdn.microsoft.com/en-us/library/windows/desktop/dd144947(v=vs.85).aspx
http://msdn.microsoft.com/en-us/library/windows/desktop/dd144909(v=vs.85).aspx
http://www.vbdotnetforums.com/graphics-gdi/22565-using-winapi-getpixel.html
Thanks
I am using LoadPicture method in my macro to load an jpg image. I want to know its width and height but the values that I get are not useful. I try to find a solution in some forums and I saw this solution:
Set oBmp = LoadPicture(FileName)
Hght = ScaleX(oBmp.Width, vbHimetric, vbPixels)
Wdth = ScaleY(oBmp.Height, vbHimetric, vbPixels)
The problem is that in powerpoint ScaleX and ScaleY are not working. At least in my powerpoint gives me the compile error: Method or data member not found.
I am also trying with this peace of code:
Dim myPic As IPictureDisp
Set myPic = LoadPicture("C:\dink_template\dinkFile\sizeimage.jpg")
Hght = myPic.height
wid = myPic.width
I check the image and his size in pixels are height = 132px and width= 338px but with that I am getting Height = 2794 and width 7154
How can I use the ScaleX/ScaleY in powerpoint? Or if I cannot use them how can pass the values to pixel?
This was fairly tricky. The dimensions you're receiving from the .Width and .Height property are actually OLE_YSIZE_HMETRIC/OLE_XSIZE_HMETRIC, which from what I can find out, are an increment of measurement representing 0.01mm.
I didn't initially see any easy workaround for this, (formula or at least a WinAPI function that would be helpful).
This should work for most users who have normal/default screen resolution settings
Function uses late-binding/does not require a reference to Publisher, although the library still needs to be available on user's machine.
Option Explicit
Sub Test()
Dim filepath$
filePath = "C:\image_file.JPG"
MsgBox "Height = " & GetImageDimensions(filepath)(0) & vbNewLine & _
"Width = " & GetImageDimensions(filepath)(1), vbOKOnly, "Dimensions"
End Sub
Function GetImageDimensions(filepath) As Variant
'Function returns an array of (Height, Width) from a specific image file path
'
Dim tmp(0 To 1) As Long
Dim oPub As Object
Set oPub = CreateObject("Publisher.Application")
'returning picture.width in OLE_YSIZE_HIMETRIC/OLE_XSIZE_HIMETRIC
' these are representing 0.01 mm
With LoadPicture(filepath)
'Multiply by 0.01 to get dimension in millimeters, then
' use the MS Publisher functions to convert millimeters -> points -> pixels
tmp(0) = 0.01 * oPub.PointsToPixels(oPub.MillimetersToPoints(.Height))
tmp(1) = 0.01 * oPub.PointsToPixels(oPub.MillimetersToPoints(.Width))
End With
GetImageDimensions = tmp
End Function
Here is a test case:
And here are the results:
UPDATE FROM COMMENTS
I get the following dimensions while debugging:
.Height = 3493
.Width = 8943
However, you indicate that you get 2794 and 7154, respectively.
I can replicate your results when I change screen resolution (e.g., 125%). The method below should resolve that discrepancy.
Attempting to use WinAPI to (hopefully) account for whatever discrepancy we're getting (pixel size, perhaps is different on your computer, which could cause this, although I'd expect the Publisher functions would account for this...)
This function with WinAPI call should work for all users, regardless of resolution
Function GetImageDimensions2(filePath As String) As Variant
'Function returns an array of (Height, Width) from a specific image file path
Dim tmp(0 To 1) As Long
'returning picture.width in OLE_YSIZE_HIMETRIC/OLE_XSIZE_HIMETRIC
' these are representing 0.01 mm
With LoadPicture(filePath)
tmp(0) = .Height / 2540 * (1440 / TwipsPerPixelY())
tmp(1) = .Width / 2540 * (1440 / TwipsPerPixelX())
End With
GetImageDimensions2 = tmp
End Function
And include these WinAPI calls in another module:
Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, _
ByVal hdc As Long) As Long
Declare Function GetDeviceCaps Lib "gdi32" (ByVal hdc As Long, _
ByVal nIndex As Long) As Long
Const HWND_DESKTOP As Long = 0
Const LOGPIXELSX As Long = 88
Const LOGPIXELSY As Long = 90
'--------------------------------------------------
Function TwipsPerPixelX() As Single
'--------------------------------------------------
'Returns the width of a pixel, in twips.
'--------------------------------------------------
Dim lngDC As Long
lngDC = GetDC(HWND_DESKTOP)
TwipsPerPixelX = 1440& / GetDeviceCaps(lngDC, LOGPIXELSX)
ReleaseDC HWND_DESKTOP, lngDC
End Function
'--------------------------------------------------
Function TwipsPerPixelY() As Single
'--------------------------------------------------
'Returns the height of a pixel, in twips.
'--------------------------------------------------
Dim lngDC As Long
lngDC = GetDC(HWND_DESKTOP)
TwipsPerPixelY = 1440& / GetDeviceCaps(lngDC, LOGPIXELSY)
ReleaseDC HWND_DESKTOP, lngDC
End Function