3
votes

I'm building a color class and I'm looking to add operations more(color, percentage) & less(color, percentage). This requires being able to add and subtract colors and I'm having a hard time with the arithmetic. How do I use RGB or HSB(HSV) or HEX to do operations like:

Operation - echo color('blue')->more('yellow', 100%);

  • blue + yellow = green

or

Operation - echo color('blue')->more('yellow', 50%);

  • blue + .5 * yellow = dark-green

For subtracting I have a very vague notion of it:

Operation - echo color('orange-yellow')->less('red', 50%);

  • orange-yellow - .5 * red = yellow

EDIT: Okay thanks for your input so far. I've tried adding CYM to each other unfortunately red (255, 0, 0) ~= (0, 1, 1) in CYM and then if you add that onto blue (0, 0, 255) ~= (1, 1, 0) it will equal (1, 2, 1) or (1, 1, 1) which is black in CYM.

I got the closest using Hue Saturation Brightness (HSB). In fact it works with every color combination except red messes up. I believe this is because red is at the beginning and end of hue (hue uses degrees [0, 360]).

Any more of your thoughts would be greatly appreciated!


EDIT 2:

Okay, so after an evening with messing around, this is a "more" method that I'm really happy with.

It uses the HSB (Hue-Saturation-Brightness) color model, Now don't ask me why I CYM didn't work. I'm a color newbie. It does seem like it would work seeing as thats how printers blend colors. I like the HSB model a lot, plus its what photoshop shows when you use the color picker.

I've added it as an answer, so let me know what you guys think! Thanks again!


Any help would be great!

Thanks, Matt

7
The primary colors of light are different than those of art. You will find yourself exerting a significant amount of effort to do 'art style' color operations on light quanta. google for 'color spaces' and try and think in terms of the physics, rather than art class edit: a great way to discover algorithms for this is to move the color sliders up and down in the gimp. I used this technique to write a rainbow algorithmuser132014
Thanks for your comment. I guess I am looking to do "art style" color operations. Is there any color space I can transform RGB or HEX or HSV into to give me "art style" operations? I feel like those are the most intuitive for adding more and less color..Matt
for HSB are you correctly wrapping around the circle? there are numerous other questions here on how to do thatjk.
See my edit - I figured out how to wrap it around a circle, thanks!Matt

7 Answers

3
votes

One solution using the RGB color space is to internally represent the colors as such - red, green, value. When a hex representation is needed, then make one from the current values and send it back.

The more and less methods then simply manipulate the current red, green, or blue values.

public function more($color, $percentage) {
    $this->color[$color] += $this->color[$color] * $percentage;
}

Convert to a hex string as

public function toHex() {
    $red = $this->color['red'];
    $green = $this->color['green'];
    $blue = $this->color['blue'];
    return sprintf("%02X%02X%02X", $red, $green, $blue);
}

Converting a string such as 'orange-green' to it's RGB components is a somewhat different problem.

2
votes

There are a lot of different models for mixing colors. I would suggest the RGBA model.

$colors = array(
    'opaque_green' => '00FF00FF',
    'transparent_blue' => '0000FF33',
);


public function toHex($red, $green, $blue, $alpha) {
    return sprintf("%02X%02X%02X%02X", $red, $green, $blue, $alpha);
}

//Reverted to british spelling :P
public function mixColours($colours) {
   $red = 0; $blue = 0; $green = 0; $alpha = 256; 
   foreach($colours as $colour) {
       $alpha_mod = hexdec(substr($colour, 6, 2)) / 256;
       $red   += $alpha_mod * hexdec(substr($colour, 0, 2));
       $green += $alpha_mod * hexdec(substr($colour, 2, 2));
       $blue  += $alpha_mod * hexdec(substr($colour, 4, 2));
   }
   $num_colours = count($colours);
   return toHex($red/$num_colours, $green/$num_colours, $blue/$num_colours, $alpha);
}

So mixColours(array($colors['opaque_green'], $colors['transparent_blue']); should give you some RGBA hex string of an aqua color.

For word conversion you could add up the total light in the color and deduce if the colour is 'dark', 'normal' or 'light'. You could also get the hue as 'green', 'orange' etc and build up a vocabulary to describe the color.

2
votes

that was fun!

Examples:

// examples
$c = Color(0x08090a)->more( 0xFF0000 , 100 )->remove(0x110000)->decrease(35)->add(0x002314);
$c->red = 0xF2;
$c->red->decrease(25);

You can check the source for all the methods, short version is add,remove,increase,decrease,more,less - full chaining on elements and colors, helper function Color() added to make things easier.

<?php

class ColorElement {

    private $value;

    public function __construct( $value = 0 )
    {
        $this->setValue( $value );
    }

    public function add( $value )
    {
        $value = self::fixValue($value);
        $this->value = self::fixValue( $this->value + $value );
        return $this;
    }

    public function remove( $value )
    {
        $value = self::fixValue($value);
        $this->value = self::fixValue( $this->value - $value );
        return $this;
    }

    public function increase( $percentage=100 )
    {
        $percentage = self::fixPercentage($percentage);
        $this->value = self::fixValue( $this->value + (int)(($this->value/100)*$percentage) );
        return $this;
    }

    public function decrease( $percentage=100 )
    {
        $percentage = self::fixPercentage($percentage);
        $this->value = self::fixValue( $this->value - (int)(($this->value/100)*$percentage) );
        return $this;
    }

    public function less( $value , $percentage=100 )
    {
        $percentage = self::fixPercentage($percentage);
        $value = self::fixValue($value);
        $this->value = self::fixValue( $this->value - (int)(($value/100)*$percentage) );
        return $this;
    }

    public function more( $value , $percentage=100 )
    {
        $percentage = self::fixPercentage($percentage);
        $value = self::fixValue($value);
        $this->value = self::fixValue( $this->value + (int)(($value/100)*$percentage) );
        return $this;
    }

    public function setValue( $value )
    {
        $this->value = self::fixValue($value);
        return $this;
    }

    public function getValue()
    {
        return $this->value;
    }

    public function __toString()
    {
        return sprintf('%02X' , $this->value);
    }

    public static function fixValue( $value )
    {
        return $value < 0 ? 0 : ($value > 255 ? 255 : $value);
    }

    public static function fixPercentage( $percentage )
    {
        return $percentage < 0 ? 0 : ($percentage > 100 ? 100 : $percentage);
    }

}

class Color {

    private $_red;
    private $_green;
    private $_blue;

    public function __construct( $hex=0x000000 )
    {
        $this->_red = new ColorElement();
        $this->_green = new ColorElement();
        $this->_blue = new ColorElement();
        $this->setColor($hex);
    }

    public function add( $hex )
    {
        list($red, $green, $blue) = self::hexRGB($hex);
        $this->_red->add( $red );
        $this->_green->add( $green );
        $this->_blue->add( $blue );
        return $this;
    }

    public function remove( $hex )
    {
        list($red, $green, $blue) = self::hexRGB($hex);
        $this->_red->remove( $red );
        $this->_green->remove( $green );
        $this->_blue->remove( $blue );
        return $this;
    }

    public function increase( $percentage=100 )
    {
        $this->_red->increase( $percentage );
        $this->_green->increase( $percentage );
        $this->_blue->increase( $percentage );
        return $this;
    }

    public function decrease( $percentage=100 )
    {
        $this->_red->decrease( $percentage );
        $this->_green->decrease( $percentage );
        $this->_blue->decrease( $percentage );
        return $this;
    }

    public function less( $hex , $percentage=100 )
    {
        list($red, $green, $blue) = self::hexRGB($hex);
        $this->_red->less( $red , $percentage );
        $this->_green->less( $green , $percentage );
        $this->_blue->less( $blue , $percentage );
        return $this;
    }

    public function more( $hex , $percentage=100 )
    {
        list($red, $green, $blue) = self::hexRGB($hex);
        $this->_red->more( $red , $percentage );
        $this->_green->more( $green , $percentage );
        $this->_blue->more( $blue , $percentage );
        return $this;
    }

    public function setColor( $hex )
    {
        list($this->red, $this->green, $this->blue) = self::hexRGB($hex);
        return $this;
    }

    public function __set( $color , $value )
    {
        if( !in_array( $color, array('red','green','blue') ) ) return;
        $this->{'_'.$color}->setValue( $value );
    }

    public function &__get( $color )
    {
        if( !in_array( $color, array('red','green','blue') ) ) return;
        return $this->{'_'.$color};
    }

    public function __toString()
    {
        return '0x' . $this->_red . $this->_green . $this->_blue;
    }

    public static function hexRGB( $hex )
    {
        return array( $hex >> 16 & 0xFF , $hex >> 8 & 0xFF , $hex & 0xFF );
    }

}

function Color( $hex=0x000000 ) {
    return new Color( $hex );
}

Hope that helps!

edit: just caught up on the thread (after doing this) and see you want 0xFFFF00 + 0x0000FF to make green, not white - sigh this won't do that, it just works with hex rgb colors - apologies!

2
votes

I think the closest you can get is the CMY colour model which, fortunately, is just the inverse of RGB.

C = 1 - R
M = 1 - G
Y = 1 - B

Now, if you assume (although CMY is cyan-magenta-yellow) that C = blue, M = red, Y = yellow, you get pretty close to your art colours. For example:

  • Blue becomes (1, 0, 0)
  • Yellow becomes (0, 0, 1)
  • Blue + Yellow becomes (1, 0, 1)
  • Converting back to RGB gives (0, 1, 0) which is green

Update:

0 to 1 are just convenient representations for no colour and full colour, equivalent to 0 to 255 of standard RGB.

For those wondering "this is obviously wrong, how is cyan = blue?", you should also realize that CMY != art colour. In fact, art colour doesn't match any primary colour model, so this is just a reasonable assumption to get the kind of colours you expect to get by mixing paints in art.

1
votes

Read about subtractive primaries, after which you can read on RYB color model.

It seems that converting from RYB to RGB is not so easy, you might want to check this calculator (and javascript behind it).

1
votes

The main problem here is understanding the concept of additive/subtractive color :

blue + yellow = green only for pigments (paint, ink and so on) because they are subtractive color

if you're using lights (additive colors) you would get : blue + yellow = white

The solution ? If you want to describe subtractive colors (paint-like combination) you have to apply the rule "sum the complementary colors" :

blue(#0000FF) +sub yellow(#FFFF00) = black(#000000)
because
blue_complement(#FFFF00) +add yellow_complement(#0000FF) = #(FFFFFF) --> white which is black complement

(in fact we get some dark brown because pigments are never perfect) So why do we get green in actual life ? because we do not use "blue" but cyan(#00FFFF) and :

cyan(#00FFFF) +sub yellow(#FFFF00) = green(#00FF00)
because
cyan_complement(#FF0000) +add yellow_complement(#0000FF) = #(FF00FF) --> magenta 

I have to add that this is a very simple way of describing the color interaction which is far more complicated...

0
votes
public function more($color, $percent = 100) {

    $percent = trim($percent, '% ');
    $percent /= 100;

    // Dictionary lookup that maps 'yellow' to #ffff00 and 'indianred' to #cd5c5c
    $color = $this->_map_color($color);

    // Creates a new Color - this will automatically compute RGB, CYM, HEX, and HSB color models
    $color = new Color($color);

    // $this->color is current color , $color is the color to be added to original

    // Allows hue to be both 360 degrees and 0 degrees
    if($this->color->hsb('h') == 0) {
        if($color->hsb('h') > 180) {
            $h = 360;
        } else {
            $h = 0;
        }
    } else {
        $h = $this->color->hsb('h');
    }

    // Computes weights of colors - original:addedColor
    // 100% added means perfect blend 50:50
    // 50% means 75:25
    // 0% means no color added 100:0
    $c2_weight = $percent / 2;
    $c1_weight = 1 - $c2_weight;

    // Compute the hue, saturation, brightness values using the weights
    $hsb[0] = round($c1_weight * $h + $c2_weight * $color->hsb('h'));
    $hsb[1] = round($c1_weight * $this->color->hsb('s') + $c2_weight * $color->hsb('s'));
    $hsb[2] = round($c1_weight * $this->color->hsb('b') + $c2_weight * $color->hsb('b'));

    $hsb = implode(' ', $hsb);

    // Change current color into the new computed HSB value.    
    $this->color = $this->color->hsb($hsb);

    return $this;
}

If the method needs more explanation or if you see something wrong here, let me know! I must say it works beautifully! I'll also note that less($color, $percent) is the same except you subtract the color instead. Less still doesn't make intuitive sense to me (yellow-green - green = brown), but I'm pretty sure the computation is correct. Thanks again for all your help!