3

I'm trying to write a function that will take an RGB(A) color and save its values in an array, with an option to exclude the alpha value. I'm having trouble figuring out how to write the regex that will match the input, but I'm thinking that something like this is what I'm looking for:

function rgb_array( $color, $include_alpha = true ) {

    $pattern = 'what goes here?';

    $color = preg_match( $pattern, $color, $matches);

    if ( $include_alpha == true && ! empty( $matches[4] ) ) {
        $color = array( $matches[1], $matches[2], $matches[3], $matches[4] );
    } else {
        $color = array( $matches[1], $matches[2], $matches[3] );
    }

    return $color;
}

I would like to be able to feed it any valid form of rgb/rgba:

rgb(0,0,0)
rgb(00,00,00)
rgb(0, 0, 0)
rgb(00, 00, 00)
rgba(0,0,0,0.5)
rgba(255, 255, 255, 1)
etc...

And have it produce an array:

[0] => '00', // red
[1] => '00', // green
[2] => '00', // blue
[3] => '1' // alpha only included if available and desired.

At the moment I'm able to accomplish this through str_replace:

$color  = 'rgb(12, 14, 85)';
$remove = array( 'rgb', 'a', '(', ')', ' ' );
$color  = str_replace( $remove, '', $color );
$color  = explode( ',', $color );

But it feels hacky and I can't find a good way to optionally include/exclude alpha.

Thanks for your help, and if there's a completely different approach than preg_match that would be better, I'm all ears.

3
  • Why not simply split on comma, then you will have an array containing each. Commented Nov 4, 2018 at 0:05
  • I'm currently doing that with explode() as I showed at the end, but if I keep that then I need a way to exclude the alpha value from the array if it's not wanted or the passed color doesn't include it. Commented Nov 4, 2018 at 0:08
  • possible duplicate of stackoverflow.com/questions/7543818/… Commented Nov 4, 2018 at 2:53

4 Answers 4

4

Not only will my answer extract the substring values that you desire, it will additionally perform a reasonably high level of validation (not 100% perfect validation). I modified the pattern from https://stackoverflow.com/a/31245990/2943403 to more precisely serve this question. I trust you will find this answer to be accurate, elegant, and direct.

If you want to strip back / simplify the pattern, this will do: (Regex101 Demo)
~^rgba?\((\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([.\d]+))?\)$~

Code: (Demo w/ $include_alpha = true) (Demo w/ $include_alpha = false) (Regex101 Demo)

function rgb_array($color, $include_alpha = true) {
    $pattern = '~^rgba?\((25[0-5]|2[0-4]\d|1\d{2}|\d\d?)\s*,\s*(25[0-5]|2[0-4]\d|1\d{2}|\d\d?)\s*,\s*(25[0-5]|2[0-4]\d|1\d{2}|\d\d?)\s*(?:,\s*([01]\.?\d*?))?\)$~';

    if (!preg_match($pattern, $color, $matches)) {
        return [];  // disqualified / no match
    }

    return array_slice($matches, 1, $include_alpha ? 4 : 3);
}

$strings = [
    'rgb(0,0,0)',
    'rgb(00,00,00)',
    'rgb(0, 0, 0)',
    'donkey eggs',
    'rgb(00, 00, 00)',
    'rgba(0,0,0,0.5)',
    'rgba(255, 255, 255, 1)'
];

foreach ($strings as $string) {
    echo "Process: $string\n";
    var_export(rgb_array($string));
    echo "\n";
}

Output:

Process: rgb(0,0,0)
array (
  0 => '0',
  1 => '0',
  2 => '0',
)
Process: rgb(00,00,00)
array (
  0 => '00',
  1 => '00',
  2 => '00',
)
Process: rgb(0, 0, 0)
array (
  0 => '0',
  1 => '0',
  2 => '0',
)
Process: donkey eggs
array (
)
Process: rgb(00, 00, 00)
array (
  0 => '00',
  1 => '00',
  2 => '00',
)
Process: rgba(0,0,0,0.5)
array (
  0 => '0',
  1 => '0',
  2 => '0',
  3 => '0.5',
)
Process: rgba(255, 255, 255, 1)
array (
  0 => '255',
  1 => '255',
  2 => '255',
  3 => '1',
)

p.s. If you want to use preg_split() you can be far less specific about your pattern. Here's a one-liner for you. (Demo)

function rgb_array($color, $include_alpha = true) {
    return array_slice(preg_split('~[^\d.]+~', $color, -1, PREG_SPLIT_NO_EMPTY), 0, $include_alpha + 3);
}
Sign up to request clarification or add additional context in comments.

2 Comments

Nice! This is clearly the most complete answer (Sorry, Nick, yours worked too!) I didn't really need validation but this function runs much faster than the other way I was validating, so that's still helpful.
@Shoelaced no problem at all - you should always accept the best answer even if that does come along after you've accepted another one.
2

If you want a regex solution, here it is:

rgba?\((\s?\d+),(\s?\d+),(\s?\d+)(?:,\s?(\d+(?:\.\d+)?))?\)

It matches 'rgb' at start, then an optional 'a' and a left parenthes, then it creates 3 similar groupes, matching an optinal White Space followed by one or more digits. The fourth Group is optional and will match one or more digits, optionally followed by a dot, and one or more digits.

The colors will be at index 1, 2, 3 and 4 (if avaiable).

2 Comments

this matches invalid rgb things such as rbga(0, 0, 0) or rgb(0, 0, 0, 0). It also only allows for a single whitespace around the digits. A slightly broader approach is something like this (though this puts things in different positional parameters depending on the type, and additionally misses a lot of other valid rgb/rgba things and also accepts a bunch of invalid stuff): /(?:(rgb)(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*)|(rgba)(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+(?:\.\d+)?)\s*))/i Again, this isn't a good solution, so don't use it, but it shows what I'm getting at.
Thanks! This worked but I ended up using the preg_split solution. Also for the record I'm validating the input separately...
2

You could use preg_split to split the string into its component parts. The regex splits the string at one of the leading rgba(, a , or the trailing ). The PREG_SPLIT_NO_EMPTY flag is used to avoid blank values in the output array.

$strings = array('rgb(0,0,0)',
'rgb(00,00,00)',
'rgb(0, 0, 0)',
'rgb(00, 00, 00)',
'rgba(0,0,0,0.5)',
'rgba(255, 255, 255, 1)');
foreach ($strings as $string) {
    print_r(preg_split('/rgba?\(\s*|\s*,\s*|\s*\)/', $string, -1, PREG_SPLIT_NO_EMPTY));
}

Output:

Array
(
    [0] => 0
    [1] => 0
    [2] => 0
)
Array
(
    [0] => 00
    [1] => 00
    [2] => 00
)
Array
(
    [0] => 0
    [1] => 0
    [2] => 0
)
Array
(
    [0] => 00
    [1] => 00
    [2] => 00
)
Array
(
    [0] => 0
    [1] => 0
    [2] => 0
    [3] => 0.5
)
Array
(
    [0] => 255
    [1] => 255
    [2] => 255
    [3] => 1
)

1 Comment

Thanks! This seems to be the simplest and I ended up using array_pop() to conditionally remove the alpha value.
0

A bit improved explode solution:

function rgb_array($color, $include_alpha = true)
{
    $color = trim($color, 'rgba()');       # "rgba(255, 255, 255, 1)" => "255, 255, 255, 1"
    $color = str_replace(' ', '', $color); # "255, 255, 255, 1"       => "255,255,255,1"
    $color = explode(',', $color);         # "255,255,255,1"          => ["255", "255", "255", "1"]
    $color[] = 1;                          # add extra value to be sure that all parsed colors have transparency

    return array_slice($color, 0, 3 + $include_alpha); # length is 3 or 4, depends on $include_alpha value
}

You can also extract numbers from the string based on the fact that built-in PHP type cast to float and int removes extra chars from the string:

function rgb_array($color, $include_alpha = true)
{
    $array = array_map('floatval', explode(',', $color));
    $array[] = 1;

    return array_slice($array, 0, 3 + $include_alpha);
}

2 Comments

The second one seems cool but oddly it ended up always returning the red value as 0...
True, my bad. There's should be extra line $color = trim($color, 'rgba()'); as a first line of the function to fix this.

Your Answer

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

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.