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?
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.
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).
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!
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:
``
That’s exactly what I was looking for. Thank you very much!