像美术课一样混合颜色(添加和减少颜色)!

3
我正在构建一个颜色类,并希望添加操作more(color, percentage)和less(color, percentage)。这需要能够相加和相减颜色,但我对算术运算感到困惑。如何使用RGB或HSB(HSV)或HEX进行操作,例如:

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

  • 蓝色 + 黄色 = 绿色

或者

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

  • 蓝色 + 0.5 * 黄色 = 暗绿色

关于减法,我有一个非常模糊的概念:

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

  • 橙黄色 - 0.5 * 红色 = 黄色

编辑: 好的,谢谢你们的意见。我试图将CYM加在一起,但不幸的是红色(255, 0, 0)~=(0, 1, 1)在CYM中,然后如果将其加到蓝色(0, 0, 255)~=(1, 1, 0)上,它将等于(1, 2, 1)或(1, 1, 1),在CYM中是黑色。

我使用了色相饱和度亮度(HSB)。实际上,除了红色出现问题之外,它适用于每种颜色组合。我认为这是因为红色位于色相的开头和结尾(色相使用度数[0,360])。

非常感谢您的帮助!


编辑2:

好的,经过一晚上的尝试,这是一个“more”方法,我非常满意。

它使用HSB(色相-饱和度-亮度)颜色模型。不要问我为什么CYM不起作用,我是一个颜色新手。看起来像打印机混合颜色的方式。我非常喜欢HSB模型,而且当你使用颜色选择器时,Photoshop会显示它。

我已经将其添加为答案,所以让我知道你们的想法!再次感谢!


任何帮助都将是极好的!

谢谢, Matt


2
光的三原色与艺术不同。你会发现,在光量子上进行“艺术风格”的颜色操作需要花费大量精力。请搜索“颜色空间”,并尝试从物理学角度思考,而非美术课程。编辑:一个很好的发现算法的方法是在GIMP中上下移动颜色滑块。我用这种技术编写了一个彩虹算法。 - user132014
谢谢您的评论。我想我正在寻找进行“艺术风格”颜色操作的方法。是否有任何可以将RGB、HEX或HSV转换为的颜色空间,以便为我提供“艺术风格”操作?我觉得这些对于添加更多和更少的颜色来说是最直观的。 - Matt
1
对于HSB,您是否正确地绕过圆圈?这里有许多其他关于如何做到这一点的问题。 - jk.
看到我的编辑了吗 - 我已经想出如何将它绕着圆圈包裹了,谢谢! - Matt
7个回答

3
使用RGB颜色空间的一种解决方案是将颜色内部表示为红色、绿色和亮度值。当需要十六进制表示时,从当前值创建一个并发送回去。
然后,moreless方法简单地操作当前的红、绿或蓝值。
public function more($color, $percentage) {
    $this->color[$color] += $this->color[$color] * $percentage;
}

将其转换为十六进制字符串,如下所示:

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

将类似于'orange-green'的字符串转换为其RGB组件是一个有些不同的问题。


1
不幸的是,将完全的黄色(#ffff00)添加到100%(全)的蓝色(#0000ff)会创建白色(#ffffff)。我正在寻找一种绿色。 - Matt
3
你可能会发现这个书籍很有趣 :),还有一些用于将一个颜色空间转换为另一个颜色空间的链接,12 - Anurag
评论区有很好的链接;我添加了一个链接到RYB计算器,这可能对Matt有帮助。 - Unreason

2

有许多不同的颜色混合模型。我建议使用RGBA模型。

$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);
}

所以mixColours(array($colors['opaque_green'], $colors['transparent_blue']);应该给你一些RGBA十六进制字符串,表示水蓝色。

对于单词转换,您可以累加颜色中的总亮度,并推断出颜色是“暗色”、“正常”还是“浅色”。您还可以将色调设置为“绿色”、“橙色”等,并建立一个词汇表来描述颜色。


1
感谢您的回复。我认为这可能有效,但是使用这个模型,“蓝色(#0000ff)+黄色(#ffff00)=灰色(#808080)”。 - Matt
是的,我实际上没有在那个问题上做真正的数学计算。我完全改变了我的答案。如果你要处理透明度,最好使用RGBA模型。我可以编写一个类似的函数来从另一个颜色中减去一个颜色。不过我真的需要回去工作了 ;) - Benbob
1
有趣的是黄色+蓝色=灰色。我在Photoshop中尝试了一下,结果变成了灰色。我想这只是光模型的问题。如果你想要颜料模型,那么这个方法就不对了。 - Benbob
我认为Tom(问题的第一个评论)说得有道理。我对这种颜色理论唯一的经验就是今天的工作。他似乎对这方面有一些理解。 - Matt
四周找一下CMYK颜色类。您不应该重复造轮子。我对色彩理论的经验是OpenGL,当然是RGBA。Python和C有很多混色库。那是我在大学里的时候。这让我想再次阅读我的讲座笔记。 - Benbob

2

我认为最接近的模型是CMY颜色模型,幸运的是,它正好是RGB的反转。

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

现在,如果你假设(尽管CMY是青色-洋红色-黄色)C = 蓝色,M = 红色,Y = 黄色,你就可以接近你的艺术颜色。例如:
- 蓝色变成(1, 0, 0) - 黄色变成(0, 0, 1) - 蓝色 + 黄色变成(1, 0, 1) - 转换回RGB得到(0, 1, 0),即绿色
更新:
0到1只是方便表示没有颜色和完整颜色,相当于标准RGB的0到255。
对于那些想知道“这显然是错误的,青色怎么可能等于蓝色?”的人,你也应该意识到CMY != 艺术颜色。实际上,艺术颜色不匹配任何原色模型,所以这只是一个合理的假设,以获得你期望通过混合艺术绘画颜料获得的颜色。

好的,我会尝试这个方法。不过有一个问题.. 我习惯于将RGB值表示为[0, 255]之间的数值,你所说的RGB值是否只是指R% = R / 255,G% = B / 255,以及B% = B / 255? - Matt
1
这没有任何意义。 (1, 0, 0) + (0, 0, 1) = (1, 0, 1) 是怎么回事?如果你有 (1, 1, 1) + (0, 0, 0) 呢?此外,青色并不等同于蓝色。在CMY中,蓝色是(1, 1, 0)。 - quantumSoup
@Matt: 我只是把它表示为百分比,以使它更简单。只需将所有内容乘以255,您就会得到标准的RGB值。@Aircule:(1,0,0)+(0,0,1)=(1,0,1),显然,只需添加组件即可。超过1的任何内容都将被剪辑。因此,(1,1,1)+(0,0,0)=(1,1,1),在RGB中为黑色-这就是当您混合所有颜料时会发生的情况。我说“假设”尽管这不是真的,因为CMY!=艺术颜色。 - casablanca

2

那很有趣!

例子:

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

你可以查看所有方法的源代码,简而言之包括添加、移除、增加、减少、更多和更少-在元素和颜色上进行完整的链接,辅助函数Color()可使事情变得更容易。
<?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 );
}

希望能对您有所帮助!编辑:在完成此操作后,我刚刚看到您想要0xFFFF00 + 0x0000FF使颜色变为绿色,而不是白色-唉,这不会做到这一点,它只适用于十六进制RGB颜色-抱歉!

哇,谢谢你的工作!是啊,我希望它尽可能直观。一旦我有了解决方案,我会发布它! - Matt

2

这里主要问题是理解加法/减法颜色的概念:

蓝色+黄色=绿色,只适用于颜料(油漆、墨水等)因为它们是减法颜色

如果你使用灯光(加法颜色),则会得到: 蓝色+黄色=白色

解决方案?如果你想描述减法颜色(类似于颜料的组合),你必须应用“求和互补色”的规则:

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

实际上,我们得到了一些暗棕色是因为颜料永远不完美。那么为什么在现实生活中我们会得到绿色?因为我们没有使用“蓝色”,而是使用了青色(#00FFFF)和:

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

我必须补充说明,这只是描述颜色相互作用的一种非常简单的方式,实际上远比这复杂...


1

0
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;
}

如果这个方法需要更多的解释或者你发现这里有什么问题,请告诉我!我必须说它运行得非常好!我还要注意一下,less($color, $percent)也是一样的,只不过你要减去颜色。对我来说,Less仍然没有直观的意义(黄绿色-绿色=棕色),但我相信计算是正确的。再次感谢你的帮助!

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接