r = $this->limit($r); $this->g = $this->limit($g); $this->b = $this->limit($b); // alpha tends to be represented [0.0 .. 1.0] rather than [0..255] $alpha = NULL === $alpha ?1.0 :$alpha; $this->setAlpha($alpha); $this->flags = $flags; // bias is up to 75% with 0.75 being "full" or "most" bias $this->setBias($bias); // process immediate flags Pixel::processFlags($this); } static public function processFlags(Pixel $p) { if(PIXEL_NOISE & $p->flags) { $p->rgbaWalk(array($p, "noise")); } if(PIXEL_ADD & $p->flags) $p->binFn(function($x,$y) { return $x+$y; }); if(PIXEL_SUB & $p->flags) $p->binFn(function($x,$y) { return $x-$y; }); if(PIXEL_OR & $p->flags) $p->binFn(function($x,$y) { return $x|$y; }); if(PIXEL_AND & $p->flags) $p->binFn(function($x,$y) { return $x&$y; }); if(PIXEL_XOR & $p->flags) $p->binFn(function($x,$y) { return $x^$y; }); } private function binFn($fn) { $b = $this->bias; $this->rgbaWalk(function($x) use ($b, $fn) { return $fn($x,$b); }); } private function rgbaWalk($callable) { $arr = array( PIXEL_RED => &$this->r, PIXEL_GREEN => &$this->g, PIXEL_BLUE => &$this->b, PIXEL_ALPHA => &$this->a ); foreach($arr as $k=>$v) { $arr[$k] = ($k & $this->flags) ?call_user_func($callable, $v) :$v; $arr[$k] = $this->limit($arr[$k]); } } private function noise($v) { return Pixel::limit(abs($v + ((rand(0,1) ?-1 :1) * rand(0,$this->bias)))); } static private function limit($x, $limit = 255) { return $limit > ($x = (int) round($x)) ?$x : $limit; } // alpha [0..1] where zero is transparent, and one is opaque public function setAlpha($f) { $this->a = $this->limit($f * 255); } // bias [0..0.50] where zero is none, and one half is max public function setBias($f) { $this->bias = $this->limit($f * 255, 192); } // value is a float [0..1] static function mix(Pixel $p1, Pixel $p2, $v1 = 0.5, $v2 = 0.5, $opts = array()) { $r = round(($p1->r * $v1) + ($p2->r * $v2)); $g = round(($p1->g * $v1) + ($p2->g * $v2)); $b = round(($p1->b * $v1) + ($p2->b * $v2)); $a = round(($p1->a * $v1) + ($p2->a * $v2)); // generate new pixel passing whatever options static methods provided if(isset($opts["flags"]) and isset($opts["bias"])) { return new Pixel($r, $g, $b, (float) $a/255, $opts["flags"], $opts["bias"]); } else if(isset($opts["flags"])) { return new Pixel($r, $g, $b, (float) $a/255, $opts["flags"]); } else { return new Pixel($r, $g, $b, (float) $a/255); } } static function generatePixelLine(Pixel $p1, Pixel $p2, $opts = array()) { // provide default total pixels, "t" $t = isset($opts["total"]) ?$opts["total"] :3; // first and second pixel are already provided $t -= 2; $arr = array(clone $p1); $toggleable = array(); // check for alternating flag, toggle existing binary functions if(isset($opts["flags"]) and (PIXEL_ALTERNATE & $opts["flags"])) { $binaryFunctions = array( PIXEL_ADD, PIXEL_SUB, PIXEL_OR, PIXEL_XOR, PIXEL_AND ); foreach($binaryFunctions as $v) { // alternate should occur immediately (second element) if($v & $opts["flags"]) { $toggleable[$v] = true; $opts["flags"] ^= $v; } } } for($i=$t;$i--;) { // toggle the binary operations foreach($toggleable as $k=>$doesntMatter) { $opts["flags"] ^= $k; } $arr[] = Pixel::mix($p1, $p2, ($i+1)/($t+1), ($t-$i)/($t+1), $opts); } $arr[] = clone $p2; return $arr; } public function __tostring() { return sprintf("%02x%02x%02x%02x", $this->r, $this->g, $this->b, $this->a); } }