14
\$\begingroup\$

256-color Xterm-compatible terminals add 240 colors on top of the usual 16 system colors. Colors 16-231 use 6 levels (0, 95, 135, 175, 215, 255) of red, green, and blue, ordered lexicographically. Colors 232-255 are simply 24 levels of gray (8...238 by 10s). To get a better idea of what I'm talking about, see this table.

The Challenge

Your goal is to make a program or function that takes, as input, rgb values, and outputs the number corresponding with the closest Xterm color to that rgb value. Since the 16 system colors (colors 0-15) are often customizable, you will be excluding them from this conversion.

To better define what the "closest" color is, use the Manhattan distance along red, green, and blue components. For example, rgb(10, 180, 90) is 20 units away from rgb(0, 175, 95) (color 35) because abs(10 - 0) + abs(180 - 175) + abs(90 - 95) == 20. If the input color is equally between two or more Xterm colors, output the Xterm color with the highest index.

Examples

 R   G   B     Xterm
  0   0   0 ==> 16
 95 135   0 ==> 64
255 255 255 ==> 231
238 238 238 ==> 255

 90 133 140 ==> 66
218 215 216 ==> 188
175 177 178 ==> 249

175   0 155 ==> 127
 75  75  75 ==> 239
 23  23  23 ==> 234
115 155 235 ==> 111

Rules

  • Standard loopholes are forbidden
  • Your program or function is allowed to take rgb values in any reasonable format, including:
    • Separate arguments for red, green, and blue
    • A list, tuple, dictionary, or similar
    • Delimiter-separated string or stdin
    • Hex colors (e.g. #ff8000)
  • You may assume that all r, g, and b, values will be integers between 0 and 255.
  • Since the 16 system colors are to be excluded from the mapping, all outputs should be in the range 16...255.

This is , so shortest code wins.

\$\endgroup\$

6 Answers 6

4
\$\begingroup\$

Haskell, 132 bytes

v=0:[95,135..255]
f c=snd$maximum[(-sum(abs<$>zipWith(-)c x),i)|(i,x)<-zip[16..]$[[r,g,b]|r<-v,g<-v,b<-v]++[[g,g,g]|g<-[8,18..238]]]

Try it online!

Takes input as a list of integers [red, green, blue].

Fairly straightforward implementation. First I build a list of the Xterm colors we're using with two list comprehensions concatenated together. The first of which handles colors 16-231 by triple iterating over v which contains the values those colors use. The second one just iterates over the grey values and puts them in all three slots. Then I index it with zip (starting at 16) and make a pair with the manhattan distance (negated) and that index and take the maximum. I used maximum because we're tie-breaking on the largest index and this way saves me one extra -.

\$\endgroup\$
3
\$\begingroup\$

Ruby, 280 180 166 164 155 bytes

->c{d=0,95,135,175,215,255
a=(0..239).map{|n|n<216?[d[n/36],d[(n%36)/6],d[n%6]]:[n*10-2152]*3}.map{|t|t.zip(c).map{|a,b|(a-b).abs}.sum}
a.rindex(a.min)+16}

Try it online!

A lambda taking the input color as an array of integers.

I had more trouble generating the Xterm colors than I expected! I'm prepared to be outgolfed embarrassingly modestly in that area. I used base conversion as a kind of compression, but the only way I know to do so in Ruby is through Integer#to_s which is a little bit awkward.

-100 bytes: Read the problem more carefully, and ignore the 16 system colors ^_^;

-14 bytes: Use hand base conversion instead of .to_s(6)

-2 bytes: Skip square brackets when declaring array

-9 bytes: Create list of Xterm colors with only one map; this also saves a plus sign and a pair of parens.

->c{
  d=0,95,135,175,215,255                 # d is the set of possible RGB values
  a=(0..239).map{|n|                     # Create the array of Xterm triplets
    n<216 ? [d[n/36],d[(n%36)/6],d[n%6]] # Convert x from base 6 to base d, or
          : [n*10-2152]*3                #   create a uniform triplet
  }.map{|t|
    t.zip(c).map{|a,b|(a-b).abs}.sum     # Map from triplets to Manhattan distance
  }
  a.rindex(a.min) +                      # Find the last index of the lowest distance
  16                                     # Offset for the exluded system colors
}
\$\endgroup\$
2
  • 1
    \$\begingroup\$ BTW, You don't need to map to the 16 system colors. Maybe I should make that clearer in the description. \$\endgroup\$
    – Beefster
    Mar 1, 2018 at 16:37
  • \$\begingroup\$ Ooh, that will help me quite a bit! I definitely missed that in the original question wording. \$\endgroup\$
    – benj2240
    Mar 1, 2018 at 16:42
2
\$\begingroup\$

Kotlin, 299 290 267 265 bytes

(16..255).associate{it to if(it<232)(it-16).let{i->listOf(0,95,135,175,215,255).let{l->listOf(l[i/36],l[(i/6)%6],l[i%6])}}else(8..238 step 10).toList()[it-232].let{listOf(it,it,it)}}.minBy{(k,v)->(it.zip(v).map{(a,b)->kotlin.math.abs(a-b)}.sum()*256)+(256-k)}!!.key

Beautified

(16..255).associate {
    it to if (it < 232) (it - 16).let { i ->
            listOf(0, 95, 135, 175, 215, 255).let { l ->
                listOf(
                        l[i / 36],
                        l[(i / 6) % 6],
                        l[i % 6])
            }
        } else (8..238 step 10).toList()[it - 232].let { listOf(it, it, it) }
}.minBy { (k, v) ->
    (it.zip(v).map { (a, b) -> kotlin.math.abs(a - b) }.sum() * 256) + (256 - k)
}!!.key

Test

data class Test(val r: Int, val g: Int, val b: Int, val out: Int)

val test = listOf(
        Test(0, 0, 0, 16),
        Test(95, 135, 0, 64),
        Test(255, 255, 255, 231),
        Test(238, 238, 238, 255),

        Test(90, 133, 140, 66),
        Test(218, 215, 216, 188),
        Test(175, 177, 178, 249),

        Test(175, 0, 155, 127),
        Test(75, 75, 75, 239),
        Test(23, 23, 23, 234),
        Test(115, 155, 235, 111)
)
fun z(it:List<Int>): Int =
(16..255).associate{it to if(it<232)(it-16).let{i->listOf(0,95,135,175,215,255).let{l->listOf(l[i/36],l[(i/6)%6],l[i%6])}}else(8..238 step 10).toList()[it-232].let{listOf(it,it,it)}}.minBy{(k,v)->(it.zip(v).map{(a,b)->kotlin.math.abs(a-b)}.sum()*256)+(256-k)}!!.key

fun main(args: Array<String>) {
    for (i in test) {
        val r = z(listOf(i.r, i.g, i.b))
        println("$i ${i.out} ==> $r")
    }
}

TIO

TryItOnline

\$\endgroup\$
2
\$\begingroup\$

Stax, 41 bytes

¬ÿ▒ú╘Σt∙Æ9φ☻ùí&BQq═IÜH∩Å╧♥f⌠óH]╨⌡≤@■Q‼Hr¼

Run and debug online!

ASCII version of 50 bytes:

"4GOW_g"{52-5*m3|^24{A*vv]3*m+{;\{E:-m|+mc|m|IH16+
\$\endgroup\$
1
\$\begingroup\$

Batch, 266 bytes

@set/ax=15,m=999
@set s=for %%b in (0 95 135 175 215 255)do @
@%s:b=r%%s:b=g%%s%call:c %* %%r %%g %%b
@for /l %%g in (8,10,238)do @call:c %* %%g %%g %%g
@echo %n%
:c
@set/ax+=1,r=%4-%1,g=%5-%2,b=%6-%3
@set/ad=%r:-=%+%g:-=%+%b:-=%
@if %d% leq %m% set/an=x,m=d
\$\endgroup\$
1
\$\begingroup\$

C (gcc), 202 192 157 150 (141 bugged) 138 134 bytes

l,m,t,i;a(c,x){x=abs(c-=i>215?i*10-2152:x*40+!!x*55);}f(r,g,b){for(i=l=240;i--;t=a(r,i/36)+a(g,i/6%6)+a(b,i%6),t<l?l=t,m=i:1);i=m+16;}

Thanks @ceilingcat

Try it online!

\$\endgroup\$
2
  • 1
    \$\begingroup\$ The problem isn't defined by the tests (which are clearly labeled as examples), so please get a new test added instead. \$\endgroup\$
    – Ton Hospel
    Mar 2, 2018 at 12:15
  • 1
    \$\begingroup\$ @TonHospel I fixed the bug now and cut down -3 bytes, but thanks for the answer \$\endgroup\$ Mar 2, 2018 at 16:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.