Глаза страшат, а руки делают

Определитель шестнадцатеричных кодов RGB (версия 1.1, 2007)


От автора

Эта статья была написана в 2006 году. После этого мне эпизодически при­хо­ди­лось заниматься программированием на VB6 и освоением VB.Net. Уже войдя во вкус хитрых математических головоломок, вернулся я как-то к этой своей утилитке и увидел в ней громоздкий нерациональный код. Ну и немного оптимизировал его.

Учтена также недоработка регистра букв: новая версия «понимает» и большие, и маленькие буквы, которые вводит пользователь в шестнадцатеричных кодах (и не ругается почём зря).

В новой редакции статьи я предлагаю новый код и несколько подробнее объ­яс­няю его. А также разбираю некоторые собственные ошибки прошлой версии.


Мой интерес к программированию начался с Visual Basic. Пытался делать на нём и «большие программы», но понял, что овчинка выделки не стоит, хотя если исхитриться, то можно почти всё (только зачем?). А вот как подручный инструмент для маленьких обиходных утилиток он иногда бывает просто необходим

Из всех известных мне и имеющихся у меня графических редакторов только Photoshop сообщает шестнадцатеричные коды цветов (да и то это место поискать надо). Простейший Paint легко определяет любой цвет на картинке, но только в значениях RGB.

Вот, например, нужно точно подогнать фон web-страницы к цвету на картинке. Или просто понравился цвет, хочу использовать именно его. Допустим, определил, что это 112-226-201. А лезть в тяжёлый Photoshop, чтобы просто поиметь код для bgcolor, ну как-то лениво.

И вот, где-то за пару часов, написал я на VB такую утилитку:

Вот из-за этой «пары часов» и пришлось потом ковыряться. Тише едешь — дальше будешь (позднейш. прим. автора).

Она конвертирует туда и обратно значения цвета, введённые вручную, а также отображает значения цвета, выбранного в системной палитре.

Хочу предоставить исходник утилиты с некоторыми комментариями для тех, кто работает с VB. Написана она на Visual Basic 5, точно так же её можно сделать на Visual Basic 6.

Текущая версия написана на VB6 (прим. автора).

Есть и дистрибутив для свободной загрузки, который просто устанавливает работающую утилиту на любой компьютер, и тогда с ней могут работать даже те, кто первый раз слышит про Visual Basic.

На форме — текстбоксы, кнопки и лейблы — для пояснений R, G, B, #, для демонстрации цвета и...

...то, что Вы видите, это не всё, есть ещё малюсенький «выдвижной» хэлп, засунутый в лейбл на этой же форме:

Ещё закинем на форму CommonDialog — этот контрол нам пригодится для вызова системной цветовой палитры.

Привожу свои исходные параметры.

Контрол

Описание

Свойства

Значения

Form

Высоту формы делаем удобной для размещения всех контролов, включая «минихэлп».
Чтобы при запуске «минихэлп» был скрыт, установим нужную высоту
в событии Form_Load().
Сделаем также
фиксированный размер.

Name
Width
BorderStyle
Caption

frmRGB
3285
1 - Fixed Single
"RGB"

Label

Подпись окна
для значений R

Name
AutoSize
Left
Top
Caption

Label1
True
280*
120
"R"

* Все измерения
даны в «твипах».

Label

Подпись окна
для значений G

Name
AutoSize
Left
Top
Caption

Label2
True
1080
120
"G"

Label

Подпись окна
для значений B

Name
AutoSize
Left
Top
Caption

Label3
True
1880
120
"B"

Label

Подпись 16-ричного окна

Name
AutoSize
Left
Top
Caption

Label4
True
120
1005
"#"

Label

Поле для показа
выбранного цвета

Name
AutoSize
Height
Left
Top
Width
BorderStyle
Caption

Label5
False
495
120
1440
1095
1 - Fixed Single
""

Label

Поле для «выдвижного
хэлпа». Этот длинный текст
у меня записан прямо в окне свойств. В коде к нему программно добавляются параметры версии
и копирайт.

Name
AutoSize
Height
Left
Top
Width
BorderStyle
Caption

Label6
False
2175
45
2280
3075
1 - Fixed Single
"        Необходимое пояснение

Перед ручным вводом 16-ричного значения окна RGB должны быть очищены.
И наоборот, перед вводом значений RGB должно быть очищено 16-ричное окно."

TextBox

Окно для значений R

Name
Height
Left
Top
Width

Text1
285
120
360
495

TextBox

Окно для значений G

Name
Height
Left
Top
Width

Text2
285
840
360
495

TextBox

Окно для значений B

Name
Height
Left
Top
Width

Text3
285
1560
360
495

TextBox

Окно для 16-ричного значения

Name
Height
Left
Top
Width

Text4
285
360
960
1095

Button

Выполнение конверсии

Name
Height
Left
Top
Width
Caption

Command1
285
1560
960
495
"OK"

Button

Открывает
системную палитру

Name
Height
Left
Top
Width
Caption

Command2
375
1320
1560
855
"Выбор"

Button

Сброс значений RGB

Name
Height
Left
Top
Width
Caption

Command3
285
2160
360
855
"<<Сброс"

Button

Сброс 16-ричного кода

Name
Height
Left
Top
Width
Caption

Command4
285
2160
960
855
"<<Сброс"

Button

Сворачивает хэлп

Name
Height
Left
Top
Width
Caption

Command5
285
1660
4560
1520
"Закрыть справку"

Button

Разворачивает хэлп

Name
Height
Left
Top
Width
Caption
Font

Command6
375
2400
1560
615
"?"
Arial 14 Bold

CommonDialog

Для выбора цвета

Name

CD1

Итак, в редакторе VB5 или VB6 объявляем переменные (для чего каждая — выяснится по ходу):

Option Explicit ' Настоятельно рекомендую выставлять эту опцию
Dim lngClr As Long, R As Long, G As Long, B As Long
Dim strClr As String, sR As String, sG As String, sB As String
Dim Response ' На эту переменную будем назначать MsgBox
Dim strHelp As String, strDovesok As String

Немножко подскажу: lngClr — числовое значение цвета, strClr — перевод его в строковое; R, G, B — числовые значения компонентов R, G, B, sR, sG, sB — их строковые представления.

Две последние переменные появились в новой версии, чтобы избежать одной «грязи», которая невидима для глаза, но засоряет память. Засорение небольшое и практически не влияет на работу программы. Я знал про него, но поленился напрягать извилины. В новой версии я стараюсь подойти к делу более профессионально.

Для перевода десятичных значений в шестнадцатеричные в VB существует встроенная функция Hex(). А вот для обратного действия нужно написать свою (функция даётся в новой, улучшенной редакции — прим. авт.):

Function Result(Stroka As String) As Long
'Конверсия 16-ричного значения в десятичное
    Dim strHx As String
    Dim iHx As Integer, iDec As Integer
    Dim lDec As Long
    For iHx = 1 To Len(Stroka)
        strHx = Mid(Stroka, iHx, 1)
        If IsNumeric(strHx) Then
            iDec = Val(strHx)
        Else
            iDec = Asc(strHx) - 55
        End If
        lDec = lDec + iDec * 16 ^ (Len(Stroka) - iHx)
    Next
    Result = lDec
End Function

Выделенные строки кода нуждаются в дополнительном пояснении.

Но начнём по порядку.

Аргумент Stroka принимает анализируемую строку шестнадцатеричного кода именно в её строковом значении. Функция будет перебирать по очереди все символы строки слева направо, iHx (Integer) — счётчик, а strHx (String) возвращает каждый следующий символ.

Для конвертирования отдельных цифр в десятичные служит переменная iDec (Integer), а для получения всего числа — переменная lDec (Long).

Как мы знаем (а кто не знает — объясняю), шестнадцатеричный код использует в качестве дополнительных цифр первые (большие) буквы латинского алфавита:

10 — A;

11 — B;

12 — C;

13 — D;

14 — E;

15 — F.

А обычные цифры соответствуют десятичным.

Булева функция IsNumeric(strHx) проверяет, является ли данный символ цифрой. Если да, то его значение приравнивается к десятичной цифре. Если же это буква...

Во всех стандартных кодировках латинский алфавит начинается с больших букв и с 65-й позиции. Если мы исследуем буквенный символ функцией Asc(), то получим его код в шрифте. То есть, Asc("A") = 65, Asc("B") = 66, и т.д.

Значит, Asc("A") - 55 = 10, Asc("B") - 55 = 11...

То есть мы получаем искомые численные значения шестнадцатеричного кода.

iDec = Asc(strHx) - 55

Теперь устанавливаем значение этого числа в зависимости от позиции.

Поясню на более простом примере десятичных чисел.

Например, 352.

Цифра 3 — это 300, то есть 3*102.

Цифра 5 — это 50, то есть 5*101.

Цифра 2 — это и есть 2, то есть 2*100.

В этой строке 3 символа, то есть её длина = 3.

Когда iHx = 1, то (Len(Stroka) - iHx) даёт 2, то есть показатель степени для 1 символа. И так далее. А чтобы применить это к шестнадцатеричному коду, нужно просто заменить 10 на 16:

iDec * 16 ^ (Len(Stroka) - iHx)

Ну и складываем это всё в одно число: с каждым витком цикла

lDec = lDec + iDec * 16 ^ (Len(Stroka) - iHx)

Так как я не математик и вообще злостный гуманитарий, то за 2 часа мне этого было не сообразить, и я написал километровый код, как школьную задачку в n действиях.

Итак, заготовка для работы у нас есть, теперь начнётся собственно работа.

Дело немного осложняется тем, что наши десятичные значения RGB представлены не единым числом, а отдельными трёхзначными значениями R, G и B, так что этого «Шалтая-Болтая» нужно ещё собрать.

А пока для разрадки немного займёмся интерфейсом.

Обработаем событие Form_Load().

Private Sub Form_Load()
    Me.Height = 2550
    R = CLng(Text1.Text)
    G = CLng(Text2.Text)
    B = CLng(Text3.Text)
    With Label5
        .BackColor = RGB(0, 0, 0)
        .ToolTipText = "#" & Format(Hex(CStr(R)), "00") _
        & Format(Hex(CStr(G)), "00") & Format(Hex(CStr(B)), "00")
    End With
    strHelp = Label6.Caption
End Sub

2550 твипов — это высота формы, скрывающая «выдвижной хэлп».

Значение последней строки объясню чуть ниже.

В принципе, больше ничего можно не писать. Но мне почему-то захотелось, чтобы утилита запускалась с уже установленными значениями чёрного («нулевого») цвета. Label5.BackColor окрашивается в выбранный цвет, а всплывающая подсказка к этому лейблу показывает его web-код.

Сделаем сразу ещё одну простую вещь: выдвигание хэлпа.

Смысл хэлпа — предупреждение, что поля, которые Вы не заполняете, нужно очищать, если в них что-то до этого было. Это потому, что программа «пляшет» от проверки пустых полей при определении, что куда нужно конвертировать (об этом чуть ниже).

Private Sub Command6_Click()
If Me.Height = 2550 Then
    strDovesok = vbCrLf & vbCrLf & "RGB, " & "версия " _
    & App.Major & "." & App.Minor & "." & App.Revision _
    & vbCrLf & " © А. Фролов, 2005–2007"
    Me.Height = 5385
Else
    strDovesok = ""
    Me.Height = 2550
End If
Label6.Caption = strHelp & strDovesok
End Sub

Здесь новый код получился немного длиннее старого. Это небольшое усложнение направлено на очистку того самого мусора, о котором я писал.

Поскольку программа может дорабатываться, номер версии — изменяться (что и произошло), я добавил эту информацию программно, а не стал изначально вписывать в готовый текст. В первой версии не было никаких переменных. Просто

Label6.Caption = Label6.Caption & vbCrLf & vbCrLf _
& "RGB, " & "версия " & App.Major & "." & App.Minor _
& "." & App.Revision & vbCrLf & " © А. Фролов, 2005–2007"

Чем это плохо?

Как видно из ветвления, эта же кнопка и убирает хэлп. В первой версии было:

Else
    Me.Height = 2550

То есть, справка убирается, но Label6.Caption остаётся с довеском. То есть при повторном открытии хэлпа она становится Label6.Caption & довесок & довесок, при следующем — Label6.Caption & довесок & довесок & довесок, и т.д. до бесконечности. Все довески находятся за границей лейбла, их не видно, но в памяти-то они есть!

Вот для этого и заносим в Form_Load() строку strHelp = Label6.Caption.

Теперь вместо Label6.Caption в коде кнопки можно использовать strHelp & strDovesok, оставляя изначальное свойство Label6.Caption без изменений.

При разворачивании на strDovesok назначаем наш довесок, при сворачивании — пустую строку.

Хотя эта же кнопка и убирает хэлп, внизу хэлпа не вредно поставить ещё одну убирающуюя кнопку (для «блондинок»). Её код:

Private Sub Command5_Click()
Label6.Caption = strHelp
Me.Height = 2550
End Sub

Как рекомендовано в хэлпе, старые значения нужно сбрасывать. «Окучим» кнопки сброса:

Private Sub Command3_Click()
Text1.Text = ""
Text2.Text = ""
Text3.Text = ""
End Sub

Private Sub Command4_Click()
Text4.Text = ""
End Sub

Вот и подобрались к самому главному.

Сначала обработаем ручной ввод. Вводим значения RGB — получаем 16-ричный код, вводим 16-ричный код — получаем значения RGB. Обработка происходит при нажатии кнопки с надписью OK, её имя в программе — Command1. То есть обрабатываем событие Command1_Click().

В десятичной системе значение RGB комбинируется из трёхзначных значений R, G и B, расположенных в обратном порядке — BGR. Схематически его можно представить как BBBGGGRRR. То есть чёрный цвет (R = 0, G = 0, B = 0) будет представлен как 0, зелёный (R = 0, G = 255, B = 0) — как 255000, а, допустим, красный (R = 255, G = 0, B = 0) — как 255. Каждый из трёхзначных «этажей» не поднимается выше 255, так что после 255 будет не 256, а 1255, а после 255255 — соответственно — 1255255.

На картинке с развёрнутым хэлпом Вы можете прочитать предупреждение, что поля, которые Вы не заполняете, нужно очищать, если в них что-то до этого было. Это потому, что программа «пляшет» от проверки пустых полей при определении, что куда нужно конвертировать. Три окна для значений R, G, B обозначены как Text1, Text2 и Text3. Окно для html-кода — как Text4.

Чтобы определить, что куда нужно конвертировать, программа проверяет пустые поля.

Процедура события Command1_Click() начинается с условия, когда Text4 пуст, то есть произойдёт конверсия RGB в HTML. Перед выполнением — «защита от дурака». Если ввести в одно из окон такую «лажу», как, например, 258, то нас туда и пошлют (GoTo Lazha). Lazha — это месиджбокс (или, по-яваскриптски, алерт), который сообщит Васе, что тот не совсем прав.

Эта часть процедуры обрабатывается встроенной функцией Hex(). Численные значения конвертируются в строковые, где нужно — добавляются нули, окончательная строка переворачивается так, как надо (то есть BGR в RGB).

Затем обрабатывается другое условие — если мы конвертируем наоборот. Тут нужно не выворачивть строку наизнанку, а аккуратненько рассовать значения по трём окошкам.

Это «рассовывание по окошкам» представляет довольно длинный код и встречается два раза: при обработке ручного ввода и при захвате значений из системной палитры. Поэтому разумно поместить его в отдельную процедуру и просто вызывать её из разных кнопок, а не дублировать код.

Записать эту процедуру нужно в раздел Declarations — туда же, где и функция Result().

Sub ConvertColorProc()
    strClr = Hex(CStr(lngClr))
    If Len(strClr) = 1 Then
        sR = "0" & strClr
        sG = "00"
        sB = "00"
    ElseIf Len(strClr) = 2 Then
        sR = strClr
        sG = "00"
        sB = "00"
    ElseIf Len(strClr) = 3 Then
        sR = Right(strClr, 2)
        sG = "0" && Left(strClr, 1)
        sB = "00"
    ElseIf Len(strClr) = 4 Then
        sR = Right(strClr, 2)
        sG = Left(strClr, 2)
        sB = "00"
    ElseIf Len(strClr) = 5 Then
        sR = Right(strClr, 2)
        sG = Mid(strClr, 2, 2)
        sB = "0" && Left(strClr, 1)
    Else
        sR = Right(strClr, 2)
        sG = Mid(strClr, 3, 2)
        sB = Left(strClr, 2)
    End If
    Text4.Text = sR & sG & sB
End Sub

Последний штрих — окраска цветового индикатора (Label5) в выбранный цвет.

Ну и обособленная процедура «защиты от лажи».

Итак, вот оно, это событие:

Private Sub Command1_Click()
On Error GoTo Lazha
If Text4.Text = "" Then
' Определение цветового кода по значениям RGB
    If CLng(Text1.Text) > 255 Xor CLng(Text2.Text) > 255 _
    Xor CLng(Text3.Text) > 255 Then
        GoTo Lazha
    Else
        R = CLng(Text1.Text)
        G = CLng(Text2.Text)
        B = CLng(Text3.Text)
        lngClr = RGB(R, G, B)
        ConvertColorProc
    End If
ElseIf Text1.Text = "" And Text2.Text = "" And Text3.Text = "" Then
    If Len(Text4.Text) < 6 Then GoTo Lazha
' Определение значений RGB по цветовому коду
    strClr = Text4.Text
    sR = Left(strClr, 2)
    sG = Mid(strClr, 3, 2)
    sB = Right(strClr, 2)
    R = Result(Format(sR, ">"))
    G = Result(Format(sG, ">"))
    B = Result(Format(sB, ">"))
    lngClr = RGB(R, G, B)
    Text1.Text = CStr(R)
    Text2.Text = CStr(G)
    Text3.Text = CStr(B)
ElseIf Len(Text1.Text) > 0 And Len(Text2.Text) > 0 _
And Len(Text3.Text) > 0 And Len(Text4.Text) > 0 Then
    GoTo Lazha
End If
    With Label5
        .BackColor = lngClr
        .ToolTipText = "#" & Text4.Text
    End With
Exit Sub
Lazha:
    Response = MsgBox("Введите корректные значения", vbOKOnly, "Лажа")
End Sub

Добавлю пояснение к выделенным строкам кода.

И функция Hex(), и наша функция Result() используют в качестве дополнительных цифр буквы заглавного регистра. Так как в HTML-коде регистр безразличен, то есть пользователи, которые привыкли пользоваться маленькими буквами (а «блондинкам» — так вообще «по барабану»). Что бы ни ввела прекрасная блондинка, аргумент функции Result() форматируется в нужный регистр: Format(sR, ">").

(Это тоже относится к улучшениям новой версии — прим. автора)

Осталось сделать выбор из системной палитры.

Кнопка «Выбор» — это Command2 для работы с системной палитрой. При её нажатии CommonDialog (назаанный в программе CD1) вызывает системную палитру, а в окнах выводятся оба варианта кода выбранного цвета.

Private Sub Command2_Click()
    CD1.CancelError = True
    On Error GoTo ErrHandler
    ' Настройка свойства Flags.
    CD1.Flags = cdlCCRGBInit
    ' Показ диалога Color.
    CD1.ShowColor
    ConvertColorProc
    R = Result(sR)
    G = Result(sG)
    B = Result(sB)
    With Label5
        .BackColor = CD1.color
        .ToolTipText = "#" & Text4.Text
    End With
    Text1.Text = CStr(R)
    Text2.Text = CStr(G)
    Text3.Text = CStr(B)
    Exit Sub

ErrHandler:
    ' Блондинка передумала.
    Exit Sub
End Sub

Ну вот и всё.

Переписал статью — и сразу появилось несколько новых пожеланий.

Во-первых, добавить захват цвета с картинки (здесь, думаю, нужно в функциях API поковыряться).

Во-вторых, сохранять банк уже использованных цветов (это работа с реестром).

И наконец, сделать так, чтобы не обязательно нужно было сбрасывать отработанные значения. Здесь нужно ввести булеву переменную... в общем, пока не буду растекаться, а когда-нибудь, возможно, сделаю новую версию.

Скачать исходник (скачано )
Скачать дистрибутив (скачано )