pixel.php 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. <?php
  2. define("PIXEL_RED", (int) pow(2,0));
  3. define("PIXEL_GREEN", (int) pow(2,1));
  4. define("PIXEL_BLUE", (int) pow(2,2));
  5. define("PIXEL_ALPHA", (int) pow(2,3));
  6. define("PIXEL_ALTERNATE", (int) pow(2,4));
  7. define("PIXEL_ADD", (int) pow(2,5));
  8. define("PIXEL_SUB", (int) pow(2,6));
  9. define("PIXEL_OR", (int) pow(2,7));
  10. define("PIXEL_AND", (int) pow(2,8));
  11. define("PIXEL_XOR", (int) pow(2,9));
  12. //define("PIXEL_FLAG", (int) pow(2,10));
  13. //define("PIXEL_FLAG", (int) pow(2,11));
  14. //define("PIXEL_FLAG", (int) pow(2,12));
  15. // ...
  16. define("PIXEL_NOISE", (int) pow(2,31));
  17. class Pixel {
  18. public $r; public $g; public $b; public $a = /* opaque */255;
  19. public $flags = /* none */ 0; public $bias = /* full */ 64;
  20. public function __construct($r, $g, $b, $alpha = NULL, $flags = 0, $bias = 0) {
  21. $this->r = $this->limit($r);
  22. $this->g = $this->limit($g);
  23. $this->b = $this->limit($b);
  24. // alpha tends to be represented [0.0 .. 1.0] rather than [0..255]
  25. $alpha = NULL === $alpha ?1.0 :$alpha;
  26. $this->setAlpha($alpha);
  27. $this->flags = $flags;
  28. // bias is up to 75% with 0.75 being "full" or "most" bias
  29. $this->setBias($bias);
  30. // process immediate flags
  31. Pixel::processFlags($this);
  32. }
  33. static public function processFlags(Pixel $p) {
  34. if(PIXEL_NOISE & $p->flags) { $p->rgbaWalk(array($p, "noise")); }
  35. if(PIXEL_ADD & $p->flags) $p->binFn(function($x,$y) { return $x+$y; });
  36. if(PIXEL_SUB & $p->flags) $p->binFn(function($x,$y) { return $x-$y; });
  37. if(PIXEL_OR & $p->flags) $p->binFn(function($x,$y) { return $x|$y; });
  38. if(PIXEL_AND & $p->flags) $p->binFn(function($x,$y) { return $x&$y; });
  39. if(PIXEL_XOR & $p->flags) $p->binFn(function($x,$y) { return $x^$y; });
  40. }
  41. private function binFn($fn) {
  42. $b = $this->bias;
  43. $this->rgbaWalk(function($x) use ($b, $fn) { return $fn($x,$b); });
  44. }
  45. private function rgbaWalk($callable) {
  46. $arr = array(
  47. PIXEL_RED => &$this->r,
  48. PIXEL_GREEN => &$this->g,
  49. PIXEL_BLUE => &$this->b,
  50. PIXEL_ALPHA => &$this->a
  51. );
  52. foreach($arr as $k=>$v) {
  53. $arr[$k] = ($k & $this->flags) ?call_user_func($callable, $v) :$v;
  54. $arr[$k] = $this->limit($arr[$k]);
  55. }
  56. }
  57. private function noise($v) {
  58. return Pixel::limit(abs($v + ((rand(0,1) ?-1 :1) * rand(0,$this->bias))));
  59. }
  60. static private function limit($x, $limit = 255) {
  61. return $limit > ($x = (int) round($x)) ?$x : $limit;
  62. }
  63. // alpha [0..1] where zero is transparent, and one is opaque
  64. public function setAlpha($f) { $this->a = $this->limit($f * 255); }
  65. // bias [0..0.50] where zero is none, and one half is max
  66. public function setBias($f) { $this->bias = $this->limit($f * 255, 192); }
  67. // value is a float [0..1]
  68. static function mix(Pixel $p1, Pixel $p2, $v1 = 0.5, $v2 = 0.5, $opts = array()) {
  69. $r = round(($p1->r * $v1) + ($p2->r * $v2));
  70. $g = round(($p1->g * $v1) + ($p2->g * $v2));
  71. $b = round(($p1->b * $v1) + ($p2->b * $v2));
  72. $a = round(($p1->a * $v1) + ($p2->a * $v2));
  73. // generate new pixel passing whatever options static methods provided
  74. if(isset($opts["flags"]) and isset($opts["bias"])) {
  75. return new Pixel($r, $g, $b, (float) $a/255, $opts["flags"], $opts["bias"]);
  76. } else if(isset($opts["flags"])) {
  77. return new Pixel($r, $g, $b, (float) $a/255, $opts["flags"]);
  78. } else {
  79. return new Pixel($r, $g, $b, (float) $a/255);
  80. }
  81. }
  82. static function generatePixelLine(Pixel $p1, Pixel $p2, $opts = array()) {
  83. // provide default total pixels, "t"
  84. $t = isset($opts["total"]) ?$opts["total"] :3;
  85. // first and second pixel are already provided
  86. $t -= 2;
  87. $arr = array(clone $p1);
  88. $toggleable = array();
  89. // check for alternating flag, toggle existing binary functions
  90. if(isset($opts["flags"]) and (PIXEL_ALTERNATE & $opts["flags"])) {
  91. $binaryFunctions = array(
  92. PIXEL_ADD,
  93. PIXEL_SUB,
  94. PIXEL_OR,
  95. PIXEL_XOR,
  96. PIXEL_AND
  97. );
  98. foreach($binaryFunctions as $v) {
  99. // alternate should occur immediately (second element)
  100. if($v & $opts["flags"]) { $toggleable[$v] = true; $opts["flags"] ^= $v; }
  101. }
  102. }
  103. for($i=$t;$i--;) {
  104. // toggle the binary operations
  105. foreach($toggleable as $k=>$doesntMatter) { $opts["flags"] ^= $k; }
  106. $arr[] = Pixel::mix($p1, $p2, ($i+1)/($t+1), ($t-$i)/($t+1), $opts);
  107. }
  108. $arr[] = clone $p2;
  109. return $arr;
  110. }
  111. public function __tostring() {
  112. return sprintf("%02x%02x%02x%02x", $this->r, $this->g, $this->b, $this->a);
  113. }
  114. }