How to get RGB from string?

Hi, in Tinderbox colors are stored as #59754d. I want a NSColor but have no idea how to get RGB from a string. Is this possible at all?

1 Like

In case it helps I have a regular expression I use in python.

Question: Can AppleScript use RegExes?

Sure. The easiest way is via my RegexAndStuff script library, available here:

Pretty sure it helps. So all that’s needed is a regex? Please share yours.

1 Like

Using a regex to split the hex string ?

as in:

tell s to set hexParts to {text 1 thru 2, text 3 thru 4, text 5 thru 6}

?

I have a feeling that that hex ⇄ rgb conversion is accessible somewhere in Tinderbox itself, though I haven’t used it for a while.

I guess you could also compose a solution more directly in AS, at least for hex string to base 10 integer conversion.

Something like this ?

-- rgbFromHexDigits :: (Int, Int, Int)
on rgbFromHexDigits(s)
    tell s to set hexParts to {text 1 thru 2, text 3 thru 4, text 5 thru 6}
    
    map(my readHex, hexParts)
end rgbFromHexDigits


--------------------------- TEST -------------------------
on run
    rgbFromHexDigits("59754d")
    
    --> {89, 117, 77}
end run


------------------------- GENERICS -----------------------

-- readHex :: String -> Int
on readHex(s)
    script go
        on |λ|(c, ne)
            set {n, e} to ne
            {n + (e * (digitToInt(c))), e * 16}
        end |λ|
    end script
    item 1 of foldr(go, {0, 1}, characters of s)
end readHex


-- digitToInt :: Char -> Int
on digitToInt(c)
    set oc to id of c
    if 48 > oc or 102 < oc then
        missing value
    else
        set dec to oc - (id of "0")
        set hexu to oc - (id of "A")
        set hexl to oc - (id of "a")
        if 9 ≥ dec then
            dec
        else if 0 ≤ hexu and 5 ≥ hexu then
            10 + hexu
        else if 0 ≤ hexl and 5 ≥ hexl then
            10 + hexl
        else
            missing value
        end if
    end if
end digitToInt


-- foldr :: (a -> b -> b) -> b -> [a] -> b
on foldr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from lng to 1 by -1
            set v to |λ|(item i of xs, v, i, xs)
        end repeat
        return v
    end tell
end foldr


-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map


-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

FWIW in osascript -l JavaScript you can use parseInt(hexString, 16)

Expand disclosure triangle to view JavaScript for Automation Source
(() => {
    "use strict";

    const main = () =>
        chunksOf(2)(
            chars("59754d")
        )
        .map(cs => readHex(cs.join("")));


    // readHex :: String -> Int
    const readHex = s =>
        // Integer value of hexadecimal expression.
        parseInt(s, 16);

    // --------------------- GENERIC ---------------------

    // chars :: String -> [Char]
    const chars = s =>
        Array.from(s);

    // chunksOf :: Int -> [a] -> [[a]]
    const chunksOf = n => {
        // xs split into sublists of length n.
        // The last sublist will be short if n
        // does not evenly divide the length of xs .
        const go = xs => {
            const chunk = xs.slice(0, n);

            return 0 < chunk.length ? (
                [chunk].concat(
                    go(xs.slice(n))
                )
            ) : [];
        };

        return go;
    };

    return main();
})();

OK. It’s quite simple:

RGBRegex = re.compile("#([0-9a-fA-F]{6})")

Ideally I’d be parsing HTML colour names as well - but there are hundreds of them (which would double the size of md2pptx). :slight_smile:

Or straight on to an NSColor:

Expand disclosure triangle to view AppleScript Source
use framework "Foundation"


-- nsRGBFFromHexDigits :: String -> (_NSTaggedPointerColor)
on nsRGBFromHexDigits(s)
    tell s
        set {r, g, b} to my map(my compose(my nOver255, my readHex), ¬
            {text 1 thru 2, text 3 thru 4, text 5 thru 6})
    end tell
    
    tell current application
        tell its NSColor
            its colorWithSRGBRed:(r) green:(g) blue:(b) alpha:(0)
        end tell
    end tell
end nsRGBFromHexDigits


-- nOver255 :: Int -> Float
on nOver255(n)
    n / 255
end nOver255

--------------------------- TEST -------------------------
on run
    nsRGBFromHexDigits("59754d")
    
    --> (_NSTaggedPointerColor) sRGB IEC61966-2.1 colorspace 0.34902 0.458824 0.301961 0
end run


------------------------- GENERICS -----------------------

-- readHex :: String -> Int
on readHex(s)
    script go
        on |λ|(c, ne)
            set {n, e} to ne
            {n + (e * (digitToInt(c))), e * 16}
        end |λ|
    end script
    item 1 of foldr(go, {0, 1}, characters of s)
end readHex


-- compose (<<<) :: (b -> c) -> (a -> b) -> a -> c
on compose(f, g)
    script
        property mf : mReturn(f)
        property mg : mReturn(g)
        on |λ|(x)
            mf's |λ|(mg's |λ|(x))
        end |λ|
    end script
end compose


-- digitToInt :: Char -> Int
on digitToInt(c)
    set oc to id of c
    if 48 > oc or 102 < oc then
        missing value
    else
        set dec to oc - (id of "0")
        set hexu to oc - (id of "A")
        set hexl to oc - (id of "a")
        if 9 ≥ dec then
            dec
        else if 0 ≤ hexu and 5 ≥ hexu then
            10 + hexu
        else if 0 ≤ hexl and 5 ≥ hexl then
            10 + hexl
        else
            missing value
        end if
    end if
end digitToInt


-- foldr :: (a -> b -> b) -> b -> [a] -> b
on foldr(f, startValue, xs)
    tell mReturn(f)
        set v to startValue
        set lng to length of xs
        repeat with i from lng to 1 by -1
            set v to |λ|(item i of xs, v, i, xs)
        end repeat
        return v
    end tell
end foldr


-- map :: (a -> b) -> [a] -> [b]
on map(f, xs)
    -- The list obtained by applying f
    -- to each element of xs.
    tell mReturn(f)
        set lng to length of xs
        set lst to {}
        repeat with i from 1 to lng
            set end of lst to |λ|(item i of xs, i, xs)
        end repeat
        return lst
    end tell
end map


-- mReturn :: First-class m => (a -> b) -> m (a -> b)
on mReturn(f)
    -- 2nd class handler function lifted into 1st class script wrapper. 
    if script is class of f then
        f
    else
        script
            property |λ| : f
        end script
    end if
end mReturn

Ah, seems you misundestood what I was looking for. I need R, G, B components from a hex string. Your regex will be useful for checking whether the input is a valid hex string. Thanks!

1 Like

Thank you, that works. However, I couldn’t make a single handler out of your generic handlers.

So I ended up adopting a handler found on MacScripter:

use AppleScript version "2.4"
use framework "Foundation"
use scripting additions

on getNSRGBFromHex(theHTMLColor) -- #xxxxxx (based on https://macscripter.net/viewtopic.php?pid=95048#p95048)
	set theHexList to "0123456789ABCDEF"
	set theNSRGBList to {}
	repeat with i from 2 to 6 by 2
		set end of theNSRGBList to ((((offset of theHTMLColor's character i in theHexList) - 1) * 16) + (offset of theHTMLColor's character (i + 1) in theHexList) - 1) / 255
	end repeat
	return theNSRGBList
end getNSRGBFromHex

It’s still pretty close…

The “6” should be “2” and then replicate that capturing group twice more. Then you have three capturing groups, one each for R, G, B.

The capturing groups are the things in round brackets. A very handy RegEx concept.

Here is a ASObjC that original was from Shane Stanley at macscripter.net

My version return rgb and hsb color a.k.a hsv, it doesn’t use any repeat loop…

use framework "Foundation"
use framework "AppKit"
use scripting additions

log (my getRGBandHSB:"6ec264")
on getRGBandHSB:colorString
	set {theResult, theRed} to (current application's NSScanner's scannerWithString:(text -6 thru -5 of colorString))'s scanHexInt:(reference)
	set {theResult, theGreen} to (current application's NSScanner's scannerWithString:(text -4 thru -3 of colorString))'s scanHexInt:(reference)
	set {theResult, theBlue} to (current application's NSScanner's scannerWithString:(text -2 thru -1 of colorString))'s scanHexInt:(reference)
	set theColor to current application's NSColor's colorWithRed:theRed / 255 green:theGreen / 255 blue:theBlue / 255 alpha:1.0
	-- set theColor to theColor's colorUsingColorSpace:(current application's NSColorSpace's genericRGBColorSpace())
	set theHue to round ((theColor's hueComponent()) * 360)
	set theSat to round ((theColor's saturationComponent()) * 100)
	set theBright to round ((theColor's brightnessComponent()) * 100)
	return {{theRed, theGreen, theBlue}, {theHue, theSat, theBright}}
end getRGBandHSB:
``
1 Like

That’s exactly what I was looking for. Thank you very much!