MacOS – Output format of the standard Mac OS Color Picker dialog (choose color)

applescriptcolormacos

It is possible to create an AppleScript that calls for the standard Mac OS Color Picker dialog (choose color) and gets the output value after user picks some color:

set the rez to choose color
set the rezStr to rez2string(rez)
set the clipboard to rezStr

on rez2string({r, g, b})
    return "(" & r & ", " & g & ", " & b & ")" as string
end rez2string

So, selected color value will be copied to the clipboard.

My question is: can I control the output of this Color Picker dialog in AppleScript? By default it returns the color value in this format: (59432, 59441, 59428), which is a 16-bit high color, I believe.

Can I specify somehow that I want to get a "standard" RGB (with 255 as a maximum value) instead? Or any other? Or 16-bit high color is the only supported output format for choose color? That would be a pity, because as far as I know there is no way to get proper RGB/Hex values from 16-bit high color as it would involve rounding and approximation.

I mean, the very same standard Color Picker dialog that is called by choose color has RGB values (along with the Hex one) on its second tab (Color Sliders), so it is capable of returning values in those formats. But are those available to get in AppleScript?

enter image description here

Update 1.

I wrote a blog-post with more details about the problem.

Update 2.

As Jon pointed out in his answer, the problem originates in the eye-dropper, so scripts are actually able to convert from 16-bit high color into "normal" 255-RGB and then HEX, but due to the fact that eye-dropper outputs incorrect value (from different color space, apparently), all further calculations are doomed.

Best Answer

First, this is the script I use:

set format to (button returned of (display dialog "Select the format of the chosen color for the clipboard." buttons {"Cancel", "HEX", "RGB"} default button 3 with icon 1 with title "Color Picker"))
set {r, g, b} to choose color
set {r, g, b} to {_16_to_8(r), _16_to_8(g), _16_to_8(b)}
if (format = "RGB") then
    set the clipboard to ("rgb(" & r & ", " & g & ", " & b & ")")
else
    set the clipboard to ("#" & {dec_to_hex(r), dec_to_hex(g), dec_to_hex(b)})
end if

on _16_to_8(n)
    return ((n / 65535) * 255) as integer
end _16_to_8

property hex_chars : "0123456789ABCDEF"'s characters
on dec_to_hex(n)
    if (n = 0) then return "00"
    return (hex_chars's item (((n div 16) mod 16) + 1)) & (hex_chars's item ((n mod 16) + 1))
end dec_to_hex

The issue you are seeing is specific to the color you chose ({236, 236, 236} / ECECEC) using the eye-dropper tool. The picker showed it as {236, 236, 236} but when returned via AppleScript, it was {59438, 59436, 59437} or something close where the RGB values are not the same (i.e., not a true gray). If you open the color picker and manually enter 236 for each of the RGB values (do not use the eye-dropper tool) and then return the color, it will return the RGB result as 3 equal values ({60652, 60652, 60652}) that will then be coerced properly by the script above.

I believe there is a rounding issue with the color picker and the eye-dropper tool as it shows, by default, 8-bit (0-255) integers instead of floating point (0.0-1.0). When you pickup the color from your example image using the eye-dropper tool and switch to the grayscale slider, it will show as 91%. This is incorrect. The value should be 92.5% but the picker doesn't show floats, only integers in the grayscale slider (you can change to floats in the RGB slider section from the action button to the right of the slider type popup button). If you are in the grayscale slider section, manually enter 92.5 and then hit the tab key to have the value accepted by the picker then hit the OK button and again, you should see the proper results back in AppleScript.

So, if the result from AppleScript is not what you are seeing in the picker, just copy the HEX value from the picker's HEX field and cancel the picker by hitting esc or the Cancel button. If you want the RGB colors, run the script again but instead of using the eye-dropper tool, paste the HEX value in the Hex field and press tab to commit the entry then hit return or OK and the proper RGB values should be on the clipboard. A pain, no doubt, but if precision is important to you, perhaps the way to go.

Off topic but using the code above, you can also convert ASCII to HEX:

string_to_hex("Convert to hexadecimal!")

property hex_chars : "0123456789ABCDEF"'s characters
on dec_to_hex(n)
    if (n = 0) then return "00"
    return (hex_chars's item (((n div 16) mod 16) + 1)) & (hex_chars's item ((n mod 16) + 1))
end dec_to_hex

on string_to_hex(s)
    set r to {}
    repeat with c in s's characters
        set end of r to dec_to_hex(ASCII number c)
    end repeat
    return r as string
end string_to_hex

UPDATE

Here's a revised script that will look to see if the user canceled the color picker with the HEX code copied to the clipboard. If both of those conditions are true, then the script will use the copied HEX code to add the RGB or HEX code to the clipboard. An extra few awkward steps (select and copy the HEX string, then cancel the color picker) but perhaps feasible for your needs.

try
    set format to (button returned of (display dialog "Select the format of the chosen color for the clipboard." buttons {"Cancel", "HEX", "RGB"} default button 3 with icon 1 with title "Color Picker"))
    set {r, g, b} to choose color
    set {r, g, b} to {_16_to_8(r), _16_to_8(g), _16_to_8(b)}
on error e number n
    if (n = -128) then --user canceled
        set clipboard_string to (get the clipboard)
        if ((clipboard_string count) = 6) then
            set {r, g, b} to hex_string_to_dec(clipboard_string)
        else
            return
        end if
    else
        log {n:n, e:e}
        return
    end if
end try
if (format = "RGB") then
    set the clipboard to ("rgb(" & r & ", " & g & ", " & b & ")")
else
    set the clipboard to ("#" & {dec_to_hex(r), dec_to_hex(g), dec_to_hex(b)})
end if

on _16_to_8(n)
    return ((n / 65535) * 255) as integer
end _16_to_8

property hex_chars : "0123456789ABCDEF"'s characters
on dec_to_hex(n)
    if (n = 0) then return "00"
    return (hex_chars's item (((n div 16) mod 16) + 1)) & (hex_chars's item ((n mod 16) + 1))
end dec_to_hex

property hex_to_dec_chars : "0.1.2.3.4.5.6.7.8.9AaBbCcDdEeFf"
on hex_to_dec(s)
    set s to s's items's reverse
    set {d, p} to {0, 0}
    repeat with c in s
        set d to d + (((offset of c in hex_to_dec_chars) div 2) * (16 ^ p))
        set p to p + 1
    end repeat
    return d as integer
end hex_to_dec

on hex_string_to_dec(s)
    if character 1 of s = "#" then set s to s's text 2 thru -1
    set r to {}
    repeat with x from 1 to length of s by 2
        set end of r to hex_to_dec(s's text x thru (x + 1))
    end repeat
    return r
end hex_string_to_dec