Skip to content

Instantly share code, notes, and snippets.

@soska
Created January 20, 2010 07:36
Show Gist options
  • Save soska/281685 to your computer and use it in GitHub Desktop.
Save soska/281685 to your computer and use it in GitHub Desktop.
<?php
/**
* Color class
*
* Plugin forn CSScaffold for color manipulation and stuff
* use like this:
* body{
* background: color-multiply(!colora,#900);
* }
*
* @author Armando Sosa (nolimit-studio.com)
* @dependencies None
**/
class Color extends Scaffold_Module
{
static private $helper;
/**
* Returns an single instance of the Color helper
*
* @return void
* @author Armando Sosa
*/
static function getHelper(){
if (!self::$helper) {
self::$helper = new ColorHelper;
}
return self::$helper;
}
/**
* The second last process, should only be getting everything
* syntaxically correct, rather than doing any heavy processing
*
* @author Armando Sosa
* @return $css string
*/
public static function process()
{
if($found = CSS::find_properties_with_value('[^:|^}]*?','color-(.*?)(\((.+?)\))?\s?'))
{
$color = self::gethelper();
$methods = $found[4];
$argss = $found[6];
foreach ($methods as $i=>$method) {
$args = explode(',',$argss[$i]);
if (method_exists($color,$method)) {
$r = call_user_func_array(array($color,$method),$args);
CSS::replace($found[3][$i], $r);
}
}
}
}
}
class ColorHelper{
function add($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'add');
}
function substract($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'substract');
}
function screen($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'screen');
}
function multiply($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'multiply');
}
function divide($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'divide');
}
function difference($top,$bottom){
return $this->__basicRGBBlend($top,$bottom,'difference');
}
function lighten($top,$ratio=.5){
// only accept numbers from 0 to 1;
$ratio = $this->__keepInrange($ratio,0,1);
return $this->__basicHSVBlend($top,$ratio,'lighten');
}
function darken($top,$ratio=.5){
// only accept numbers from 0 to 1;
$ratio = $this->__keepInrange($ratio,0,1);
return $this->__basicHSVBlend($top,$ratio,'darken');
}
function invert($top,$ratio=1){
// only accept numbers from 0 to 1;
$ratio = $this->__keepInrange($ratio,0,1);
return $this->__basicRGBBlend($top,$ratio,'invert');
}
function shadow($top,$ratio=.5){
// only accept numbers from 0 to 1;
$ratio = $this->__keepInrange($ratio,0,1);
return $this->__basicHSVBlend($top,$ratio,'shadow');
}
function illuminate($top,$ratio=.5){
// only accept numbers from 0 to 1;
$ratio = $this->__keepInrange($ratio,0,1);
return $this->__basicHSVBlend($top,$ratio,'illuminate');
}
function contrast($top,$ratio=.5){
// only accept numbers from 0 to 2;
$ratio = $this->__keepInrange($ratio,0,2);
return $this->__basicHSVBlend($top,$ratio,'contrast');
}
function complement($color){
if (!$this->__isHSV($color)) {
$color = $this->__RGBToHSV($this->__hexToRGB($color));
}
$color = $this->adjust($color,'h',180);
return $this->__RGBToHex($this->__HSVToRGB($color));
}
function __basicRGBBlend($top,$bottom,$type = 'multiply'){
// first, transform colors into rgb arrays
$top = $this->__hexToRGB($top);
$bottom = $this->__hexToRGB($bottom);
$channels = array('r','g','b');
$result = array();
foreach ($channels as $channel) {
$a = $top[$channel];
$b = $bottom[$channel];
switch ($type) {
case 'multiply':
$result[$channel] = floor($a * $b / 255);
break;
case 'screen':
$result[$channel] = floor(255 - (((255 - $a) * (255 - $b)) / 255));
break;
case 'add':
$value = $a+$b;
$result[$channel] = $this->__keepInRGBRange($value);
break;
case 'substract':
$value = $a-$b;
$result[$channel] = $this->__keepInRGBRange($value);
break;
case 'divide':
if ($b == 0) $b = 1;
$value = ceil(($a/$b))*4;
$result[$channel] = $this->__keepInRGBRange($value);
break;
case 'difference':
$value = $a-$b;
if ($value < 0) {
$value = $b-$a;
}
$result[$channel] = $value;
break;
case 'invert':
$value = $a-(255*$b);
if ($value<0) {
$value = 1-$value;
}
$result[$channel] = $this->__keepInRGBRange($value);
break;
default:
$result[$channel] = $top[$channel];
break;
}
}
return $this->__RGBToHex($result);
}
function __basicHSVBlend($top,$ratio = 1,$type = 'lighten'){
if (!$this->__isHSV($top)) {
$top = $this->__RGBToHSV($this->__hexToRGB($top));
}
switch ($type) {
case 'lighten':
$top = $this->adjust($top,'v',($ratio * 100));
break;
case 'darken':
$top = $this->adjust($top,'v',1-($ratio * 100));
break;
case 'illuminate':
$top = $this->adjust($top,'v',($ratio * 100));
$top = $this->adjust($top,'s',1-($ratio * 50));
// yellowish colors deserve a special treatment,
if ($top['h']>29 && $top['h']<61) {
$top = $this->adjust($top,'s',($ratio * 15));
$top = $this->adjust($top,'h',$ratio * 10);
}
break;
case 'shadow':
$top = $this->adjust($top,'v',1-($ratio * 100));
$top = $this->adjust($top,'s',($ratio * 50));
if ($top['h']>29 && $top['h']<70) {
$top = $this->adjust($top,'v',($ratio * 75));
$top = $this->adjust($top,'h',1-($ratio * 10));
}
break;
case 'contrast':
$ammount = (100-$top['v'])*$ratio;
if ($top['v'] == 50) {
$ammount = 50;
}elseif ($top['v'] == 100) {
$ammount = -50;
} elseif ($top['v']>50) {
$compensation = round(($top['v']-50)/2.5);
$ammount = 1-$ammount*$compensation;
}
$top = $this->adjust($top,'v',$ammount);
$top = $this->adjust($top,'s',1-($ammount/4));
// $top = $this->adjust($top,'v',1-($top['v']-(50*$ratio)));
break;
}
return $this->__RGBToHex($this->__HSVToRGB($top));
}
function __keepInRGBRange($value){
return $this->__keepInRange($value,0,255);
}
function __keepInRange($value,$min,$max){
if ($value <$min) {
return $min;
}elseif ($value>$max) {
return $max;
}
return $value;
}
function __RGBToHex($rgb){
if (!$this->__isRGB($rgb)) {
return false;
}
$hex = "#";
$channels = array('r','g','b');
foreach ($channels as $channel) {
$hex.=str_pad(dechex($rgb[$channel]),2,"0",STR_PAD_LEFT);
}
return $hex;
}
function __hexToRGB($hex){
if (!is_string($hex) && !is_array($hex)) {
$hex = array('r'=>$hex,'g'=>$hex,'b'=>$hex);
}
if ($this->__isRGB($hex)) {
return $hex;
}
$hex = trim(str_replace('#','',$hex));
$len = strlen($hex);
if ( $len == 3) {
$chopsize = 1;
}elseif ( $len == 6) {
$chopsize = 2;
}else{
return false;
}
$color = array();
list($color['r'],$color['g'],$color['b']) = str_split($hex,$chopsize);
foreach ($color as &$channel) {
if (strlen($channel) == 1) {
$channel.=$channel;
}
$channel = hexdec($channel);
}
return $color;
}
function __isRGB($color){
return (is_array($color) && isset($color['r']) && isset($color['g']) && isset($color['b']));
}
function __isHSV($color){
return (is_array($color) && isset($color['h']) && isset($color['s']) && isset($color['v']));
}
function __RGBToHSV($rgb){
if (!$this->__isRGB($rgb)) {
$rgb = $this->__hexToRGB($rgb);
}
$hsv = array();
extract($rgb);
$max = max($r,$g,$b);
$min = min($r,$g,$b);
$difference = $max-$min;
$hsv['v'] = round(($max/255) * 100);
if ($difference == 0) {
$hsv['s'] = $max;
$hsv['h'] = false;
}else{
$hsv['s'] = round($difference/$max * 100);
switch ($max) {
case $r:
$gminusb = ($g-$b);
if ($gminusb < 2){
$hsv['h'] = 360 - $gminusb;
}else{
$hsv['h'] = $gminusb / $difference;
}
// pr(compact('hsv','rgb','difference'));
// die;
break;
case $g:
$hsv['h'] = 2 + ($b-$r) / $difference;
break;
case $b:
$hsv['h'] = 4 + ($r-$g) / $difference;
break;
}
$hsv['h'] = round($hsv['h'] * 60);
if ($hsv['h'] > 360) {
$hsv['h'] = 360;
}
if ($hsv['h'] < 0) {
$hsv['h'] += 360;
}
// pr($hsv);die;
}
return $hsv;
}
function __HSVToRGB($hsv){
extract($hsv);
$v = $v/100;
$s = $s/100;
if ($h == false) {
$v = floor($v*255);
list($r,$g,$b) = array($v,$v,$v);
}else{
$h = $h / 60;
$i = floor($h);
$f = $h - $i;
if (!($i & 1)) {
$f = 1-$f;
}
$m = $v * (1 - $s);
$n = $v * (1 - $s * $f);
$v = floor(255 * $v);
$m = floor(255 * $m);
$n = floor(255 * $n);
switch ($i) {
case 6:
case 0:
$r = $v;
$g = $n;
$b = $m;
break;
case 1:
$r = $n;
$g = $v;
$b = $m;
break;
case 2:
$r = $m;
$g = $v;
$b = $n;
break;
case 3:
$r = $m;
$g = $n;
$b = $v;
break;
case 4:
$r = $n;
$g = $m;
$b = $v;
break;
case 5:
$r = $v;
$g = $m;
$b = $n;
break;
}
}
return compact('r','g','b');
}
function adjust($hsv,$channel = 'h',$ratio = 1){
if ($channel == "h"){
$hsv['h'] += round($ratio);
if ($hsv['h'] < 0) {
$hsv['h'] += 360;
}
if ($hsv['h'] > 360) {
$hsv['h'] -= 360;
}
return $hsv;
}
$hsv[$channel] += round($ratio);
$hsv[$channel] = $this->__keepInRange($hsv[$channel],0,100);
return $hsv;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment