VBA Получить цвет пикселя

Я импортирую изображения в Excel и пытаюсь рассчитать средний цвет для определенной пользователем области изображения. Для этого пользователь создает границу, а затем я перебираю пиксели экрана, чтобы увидеть, попадают ли они в эту границу. Если да, то RGB этого пикселя добавляется в коллекцию перед усреднением в конце.

У меня в целом все это работает, однако по какой-то причине мой код неправильно определяет цвет пикселя. То, что должно быть желтыми или синими пикселями (или любым другим цветом), вместо этого записывается как оттенок серого (чаще всего 16777215 или 13948116 в десятичном значении Windows).

Я предполагаю, что у меня что-то не так с функцией PixelColor, которая предназначена для получения цвета пикселя для координат XY, которые я ввожу в нее (значения, такие как -1107 или 830), но вместо этого должна возвращать цвет некоторого другие пиксели. Я попытался адаптировать это из кода, который определяет цвет на основе пикселя, в котором находится курсор мыши, но явно ошибся, пытаясь передать ему координаты XY, а не получить это из положения курсора.

Код для получения цвета пикселя, а также преобразования в RGB выглядит следующим образом:

Private Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hdc As Long, 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
Private Declare PtrSafe Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Type POINT
    X As Long
    Y As Long
End Type

Private Function PixelColor(ByVal X As Long, ByVal Y As Long) As Long

Dim lDC As Variant

lDC = GetWindowDC(0)
PixelColor = GetPixel(lDC, X, Y)

End Function

Они вводятся в код, который перебирает ячейки и использует координаты XY, такие как -1107 или 830:

Sub AverageColour()

'loop through pixels
For i = MinX To MaxX
    For j = MinY To MaxY
        'check if pixel falls within user-defined polygon
        If udfPointInPolygon(i, j, Range("B2:C21")) = True Then
            PointColor = PixelColor(i, j)
            collR.Add CStr(m_RGB_Red(PointColor))
            collG.Add CStr(m_RGB_Green(PointColor))
            collB.Add CStr(m_RGB_Blue(PointColor))
        End If
    Next j
Next i

'calculate collection averages
totalR = 0
totalG = 0
totalB = 0

For k = 1 To collR.Count
    totalR = totalR + collR(k)
Next k

For k = 1 To collG.Count
    totalG = totalG + collG(k)
Next k

For k = 1 To collB.Count
    totalB = totalB + collB(k)
Next k

averageR = totalR / collR.Count
averageG = totalG / collG.Count
averageB = totalB / collB.Count

End Sub

Любые идеи, где я ошибся, были бы замечательными... заранее спасибо за вашу помощь!


person Nat Aes    schedule 03.05.2020    source источник
comment
Вы пытаетесь получить цвет пикселя на экране?   -  person FaneDuru    schedule 03.05.2020
comment
Я не уверен, что вы имеете в виду под этим @FaneDuru. Выполнение этого кода с координатами XY -1105, 815 выводит цвет 16777215 (серый). На самом деле я получил эти координаты с помощью пипетки, которая вместе с цветом (желтым) выводит координаты.   -  person Nat Aes    schedule 03.05.2020
comment
Я имею в виду, что GetPixel API может получить цвет пикселя растрового объекта. Я знаю, что его можно загрузить с помощью LoadPicture, а затем ему нужен CreateCompatibleDC API для создания lDC... После получения PixelColor память должна быть освобождена с помощью DeleteDC API. Я не могу разместить здесь необходимые API и код... Если интересно, я могу создать функцию, используя вышеописанный процесс.   -  person FaneDuru    schedule 03.05.2020
comment
Если бы вы могли, это было бы фантастикой!   -  person Nat Aes    schedule 04.05.2020


Ответы (1)


Что я хотел отметить, так это то, что GetPixel API работает с растровым объектом. На картинке. Я не хочу сказать, что имея картинку на листе и пытаясь использовать её прямо на экране (не на растровом объекте) функция не вернётся корректно. Я просто думаю, что это может быть не так. Некоторое время назад я определял цвет некоторых пикселей для изображения (не загруженного в Excel) с помощью VBA следующим образом:

Необходимые функции API (поверх модуля, в части объявлений):

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 PixelColorBis(objPict As Object, ByVal X As Long, ByVal Y As Long) As Long
 Dim lDC As Variant

 lDC = CreateCompatibleDC(0)
 SelectObject lDC, objPict.Handle
 PixelColorBis = GetPixel(lDC, X, Y)

 DeleteDC lDC
End Function

И процедура тестирования должна выглядеть так:

Sub testPixelColor()
  Dim objPict As Object, pictPath As String, objImage As Object

  pictPath = ThisWorkbook.path & "\Poza Carte Munca.jpg" ' use here your picture path
  'Obtain the picture dimensions in pixels______________________________________________________
  Set objImage = CreateObject("WIA.ImageFile")
  objImage.LoadFile ThisWorkbook.path & "\Poza Carte Munca.jpg"
  Debug.Print objImage.width, objImage.height ' picture dimensions in pixels
  'using the above dimensions you can iterate between the width pixels number and the heigh, too.
  '_____________________________________________________________________________________________

  Set objPict = LoadPicture(pictPath) 'the picture object to be processed 

  Debug.Print PixelColorBis(objPict, 2, 3) 'I just used sample X and Y only to check the function functionality
End Sub

У меня нет времени поэкспериментировать с вашим способом и понять, почему он не возвращает то, что вам нужно. Я бы только предложил протестировать мой код и, если он возвращает то, что вам нужно, найти способ использовать объект изображения, даже если он загружен вместо прямоугольника экрана... Это только предложение!

person FaneDuru    schedule 04.05.2020
comment
AFAIK VBA7 требует, чтобы каждый аргумент hdc был LongPtr, поэтому (1) объявляйте Dim lDC As LongPtr в PixelColorBis(), тогда как (2) вызовы API читаются как: #If VBA7 Then: Declare PtrSafe Function CreateCompatibleDC Lib "gdi32" (ByVal hdc As LongPtr) As LongPtr: Declare PtrSafe Function SelectObject Lib "gdi32" (ByVal hdc As LongPtr, ByVal hObject As LongPtr) As LongPtr: Declare PtrSafe Function GetPixel Lib "gdi32" (ByVal hdc As LongPtr, ByVal X As Long, ByVal Y As Long) As Long: Declare PtrSafe Function DeleteDC Lib "gdi32" (ByVal hdc As LongPtr) As Long: #End If - person T.M.; 08.05.2020
comment
Конечно, вам нужна условная компиляция только для использования в обеих системах, где старые 32-битные вызовы API перечислены в блоке #Else. - person T.M.; 11.05.2020