CubicBezier.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412
  1. <?php
  2. namespace Grafika\Gd\DrawingObject;
  3. use Grafika\DrawingObject\CubicBezier as Base;
  4. use Grafika\DrawingObjectInterface;
  5. use Grafika\Gd\Image;
  6. use Grafika\ImageInterface;
  7. /**
  8. * Class CubicBezier
  9. * @package Grafika
  10. */
  11. class CubicBezier extends Base implements DrawingObjectInterface
  12. {
  13. /**
  14. * @param ImageInterface $image
  15. * @return Image
  16. */
  17. public function draw($image)
  18. {
  19. // Localize vars
  20. $width = $image->getWidth();
  21. $height = $image->getHeight();
  22. $gd = $image->getCore();
  23. list($x0, $y0) = $this->point1;
  24. list($x1, $y1) = $this->control1;
  25. list($x2, $y2) = $this->control2;
  26. list($x3, $y3) = $this->point2;
  27. $this->plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3);
  28. $type = $image->getType();
  29. $file = $image->getImageFile();
  30. return new Image($gd, $file, $width, $height, $type); // Create new image with updated core
  31. }
  32. protected function plot($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
  33. { /* plot any cubic Bezier curve */
  34. $n = 0;
  35. $i = 0;
  36. $xc = $x0 + $x1 - $x2 - $x3;
  37. $xa = $xc - 4 * ($x1 - $x2);
  38. $xb = $x0 - $x1 - $x2 + $x3;
  39. $xd = $xb + 4 * ($x1 + $x2);
  40. $yc = $y0 + $y1 - $y2 - $y3;
  41. $ya = $yc - 4 * ($y1 - $y2);
  42. $yb = $y0 - $y1 - $y2 + $y3;
  43. $yd = $yb + 4 * ($y1 + $y2);
  44. $fx0 = $x0;
  45. $fx1 = 0;
  46. $fx2 = 0;
  47. $fx3 = 0;
  48. $fy0 = $y0;
  49. $fy1 = 0;
  50. $fy2 = 0;
  51. $fy3 = 0;
  52. $t1 = $xb * $xb - $xa * $xc;
  53. $t2 = 0;
  54. $t = array();
  55. /* sub-divide curve at gradient sign changes */
  56. if ($xa == 0) { /* horizontal */
  57. if (abs($xc) < 2 * abs($xb)) {
  58. $t[$n++] = $xc / (2.0 * $xb);
  59. } /* one change */
  60. } else {
  61. if ($t1 > 0.0) { /* two changes */
  62. $t2 = sqrt($t1);
  63. $t1 = ($xb - $t2) / $xa;
  64. if (abs($t1) < 1.0) {
  65. $t[$n++] = $t1;
  66. }
  67. $t1 = ($xb + $t2) / $xa;
  68. if (abs($t1) < 1.0) {
  69. $t[$n++] = $t1;
  70. }
  71. }
  72. }
  73. $t1 = $yb * $yb - $ya * $yc;
  74. if ($ya == 0) { /* vertical */
  75. if (abs($yc) < 2 * abs($yb)) {
  76. $t[$n++] = $yc / (2.0 * $yb);
  77. } /* one change */
  78. } else {
  79. if ($t1 > 0.0) { /* two changes */
  80. $t2 = sqrt($t1);
  81. $t1 = ($yb - $t2) / $ya;
  82. if (abs($t1) < 1.0) {
  83. $t[$n++] = $t1;
  84. }
  85. $t1 = ($yb + $t2) / $ya;
  86. if (abs($t1) < 1.0) {
  87. $t[$n++] = $t1;
  88. }
  89. }
  90. }
  91. for ($i = 1; $i < $n; $i++) /* bubble sort of 4 points */ {
  92. if (($t1 = $t[$i - 1]) > $t[$i]) {
  93. $t[$i - 1] = $t[$i];
  94. $t[$i] = $t1;
  95. $i = 0;
  96. }
  97. }
  98. $t1 = -1.0;
  99. $t[$n] = 1.0; /* begin / end point */
  100. for ($i = 0; $i <= $n; $i++) { /* plot each segment separately */
  101. $t2 = $t[$i]; /* sub-divide at $t[$i-1], $t[$i] */
  102. $fx1 = ($t1 * ($t1 * $xb - 2 * $xc) - $t2 * ($t1 * ($t1 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
  103. $fy1 = ($t1 * ($t1 * $yb - 2 * $yc) - $t2 * ($t1 * ($t1 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
  104. $fx2 = ($t2 * ($t2 * $xb - 2 * $xc) - $t1 * ($t2 * ($t2 * $xa - 2 * $xb) + $xc) + $xd) / 8 - $fx0;
  105. $fy2 = ($t2 * ($t2 * $yb - 2 * $yc) - $t1 * ($t2 * ($t2 * $ya - 2 * $yb) + $yc) + $yd) / 8 - $fy0;
  106. $fx0 -= $fx3 = ($t2 * ($t2 * (3 * $xb - $t2 * $xa) - 3 * $xc) + $xd) / 8;
  107. $fy0 -= $fy3 = ($t2 * ($t2 * (3 * $yb - $t2 * $ya) - 3 * $yc) + $yd) / 8;
  108. $x3 = floor($fx3 + 0.5);
  109. $y3 = floor($fy3 + 0.5); /* scale bounds to int */
  110. if ($fx0 != 0.0) {
  111. $fx1 *= $fx0 = ($x0 - $x3) / $fx0;
  112. $fx2 *= $fx0;
  113. }
  114. if ($fy0 != 0.0) {
  115. $fy1 *= $fy0 = ($y0 - $y3) / $fy0;
  116. $fy2 *= $fy0;
  117. }
  118. if ($x0 != $x3 || $y0 != $y3) /* segment $t1 - $t2 */ {
  119. $this->plotCubicSegment($gd, $x0, $y0, $x0 + $fx1, $y0 + $fy1, $x0 + $fx2, $y0 + $fy2, $x3, $y3);
  120. }
  121. $x0 = $x3;
  122. $y0 = $y3;
  123. $fx0 = $fx3;
  124. $fy0 = $fy3;
  125. $t1 = $t2;
  126. }
  127. }
  128. protected function plotCubicSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
  129. { /* plot limited anti-aliased cubic Bezier segment */
  130. $f = 0;
  131. $fx = 0;
  132. $fy = 0;
  133. $leg = 1;
  134. $sx = $x0 < $x3 ? 1 : -1;
  135. $sy = $y0 < $y3 ? 1 : -1; /* step direction */
  136. $xc = -abs($x0 + $x1 - $x2 - $x3);
  137. $xa = $xc - 4 * $sx * ($x1 - $x2);
  138. $xb = $sx * ($x0 - $x1 - $x2 + $x3);
  139. $yc = -abs($y0 + $y1 - $y2 - $y3);
  140. $ya = $yc - 4 * $sy * ($y1 - $y2);
  141. $yb = $sy * ($y0 - $y1 - $y2 + $y3);
  142. $ab = 0;
  143. $ac = 0;
  144. $bc = 0;
  145. $ba = 0;
  146. $xx = 0;
  147. $xy = 0;
  148. $yy = 0;
  149. $dx = 0;
  150. $dy = 0;
  151. $ex = 0;
  152. $px = 0;
  153. $py = 0;
  154. $ed = 0;
  155. $ip = 0;
  156. $EP = 0.01;
  157. /* check for curve restrains */
  158. /* slope P0-P1 == P2-P3 and (P0-P3 == P1-P2 or no slope change) */
  159. assert(($x1 - $x0) * ($x2 - $x3) < $EP && (($x3 - $x0) * ($x1 - $x2) < $EP || $xb * $xb < $xa * $xc + $EP));
  160. assert(($y1 - $y0) * ($y2 - $y3) < $EP && (($y3 - $y0) * ($y1 - $y2) < $EP || $yb * $yb < $ya * $yc + $EP));
  161. if ($xa == 0 && $ya == 0) { /* quadratic Bezier */
  162. $sx = floor((3 * $x1 - $x0 + 1) / 2);
  163. $sy = floor((3 * $y1 - $y0 + 1) / 2); /* new midpoint */
  164. $this->plotQuadSegment($gd, $x0, $y0, $sx, $sy, $x3, $y3);
  165. return;
  166. }
  167. $x1 = ($x1 - $x0) * ($x1 - $x0) + ($y1 - $y0) * ($y1 - $y0) + 1; /* line lengths */
  168. $x2 = ($x2 - $x3) * ($x2 - $x3) + ($y2 - $y3) * ($y2 - $y3) + 1;
  169. do { /* loop over both ends */
  170. $ab = $xa * $yb - $xb * $ya;
  171. $ac = $xa * $yc - $xc * $ya;
  172. $bc = $xb * $yc - $xc * $yb;
  173. $ip = 4 * $ab * $bc - $ac * $ac; /* self intersection loop at all? */
  174. $ex = $ab * ($ab + $ac - 3 * $bc) + $ac * $ac; /* P0 part of self-intersection loop? */
  175. $f = $ex > 0 ? 1 : sqrt(1 + 1024 / $x1); /* calculate resolution */
  176. $ab *= $f;
  177. $ac *= $f;
  178. $bc *= $f;
  179. $ex *= $f * $f; /* increase resolution */
  180. $xy = 9 * ($ab + $ac + $bc) / 8;
  181. $ba = 8 * ($xa - $ya);/* init differences of 1st degree */
  182. $dx = 27 * (8 * $ab * ($yb * $yb - $ya * $yc) + $ex * ($ya + 2 * $yb + $yc)) / 64 - $ya * $ya * ($xy - $ya);
  183. $dy = 27 * (8 * $ab * ($xb * $xb - $xa * $xc) - $ex * ($xa + 2 * $xb + $xc)) / 64 - $xa * $xa * ($xy + $xa);
  184. /* init differences of 2nd degree */
  185. $xx = 3 * (3 * $ab * (3 * $yb * $yb - $ya * $ya - 2 * $ya * $yc) - $ya * (3 * $ac * ($ya + $yb) + $ya * $ba)) / 4;
  186. $yy = 3 * (3 * $ab * (3 * $xb * $xb - $xa * $xa - 2 * $xa * $xc) - $xa * (3 * $ac * ($xa + $xb) + $xa * $ba)) / 4;
  187. $xy = $xa * $ya * (6 * $ab + 6 * $ac - 3 * $bc + $ba);
  188. $ac = $ya * $ya;
  189. $ba = $xa * $xa;
  190. $xy = 3 * ($xy + 9 * $f * ($ba * $yb * $yc - $xb * $xc * $ac) - 18 * $xb * $yb * $ab) / 8;
  191. if ($ex < 0) { /* negate values if inside self-intersection loop */
  192. $dx = -$dx;
  193. $dy = -$dy;
  194. $xx = -$xx;
  195. $yy = -$yy;
  196. $xy = -$xy;
  197. $ac = -$ac;
  198. $ba = -$ba;
  199. } /* init differences of 3rd degree */
  200. $ab = 6 * $ya * $ac;
  201. $ac = -6 * $xa * $ac;
  202. $bc = 6 * $ya * $ba;
  203. $ba = -6 * $xa * $ba;
  204. $dx += $xy;
  205. $ex = $dx + $dy;
  206. $dy += $xy; /* error of 1st step */
  207. for ($fx = $fy = $f; $x0 != $x3 && $y0 != $y3;) {
  208. $y1 = min($xy - $dx, $dy - $xy);
  209. $ed = max($xy - $dx, $dy - $xy); /* approximate error distance */
  210. $ed = $f * ($ed + 2 * $ed * $y1 * $y1 / (4 * $ed * $ed + $y1 * $y1));
  211. $y1 = 255 * abs($ex - ($f - $fx + 1) * $dx - ($f - $fy + 1) * $dy + $f * $xy) / $ed;
  212. if ($y1 < 256) {
  213. $this->setPixel($gd, $x0, $y0, $y1 / 255);
  214. } /* plot curve */
  215. $px = abs($ex - ($f - $fx + 1) * $dx + ($fy - 1) * $dy); /* pixel intensity x move */
  216. $py = abs($ex + ($fx - 1) * $dx - ($f - $fy + 1) * $dy); /* pixel intensity y move */
  217. $y2 = $y0;
  218. do { /* move sub-steps of one pixel */
  219. if ($ip >= -$EP) /* intersection possible? -> check.. */ {
  220. if ($dx + $xx > $xy || $dy + $yy < $xy) {
  221. goto exits;
  222. }
  223. } /* two x or y steps */
  224. $y1 = 2 * $ex + $dx; /* save value for test of y step */
  225. if (2 * $ex + $dy > 0) { /* x sub-step */
  226. $fx--;
  227. $ex += $dx += $xx;
  228. $dy += $xy += $ac;
  229. $yy += $bc;
  230. $xx += $ab;
  231. } else {
  232. if ($y1 > 0) {
  233. goto exits;
  234. }
  235. } /* tiny nearly cusp */
  236. if ($y1 <= 0) { /* y sub-step */
  237. $fy--;
  238. $ex += $dy += $yy;
  239. $dx += $xy += $bc;
  240. $xx += $ac;
  241. $yy += $ba;
  242. }
  243. } while ($fx > 0 && $fy > 0); /* pixel complete? */
  244. if (2 * $fy <= $f) { /* x+ anti-aliasing pixel */
  245. if ($py < $ed) {
  246. $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
  247. } /* plot curve */
  248. $y0 += $sy;
  249. $fy += $f; /* y step */
  250. }
  251. if (2 * $fx <= $f) { /* y+ anti-aliasing pixel */
  252. if ($px < $ed) {
  253. $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
  254. } /* plot curve */
  255. $x0 += $sx;
  256. $fx += $f; /* x step */
  257. }
  258. }
  259. break; /* finish curve by line */
  260. exits:
  261. if (2 * $ex < $dy && 2 * $fy <= $f + 2) { /* round x+ approximation pixel */
  262. if ($py < $ed) {
  263. $this->setPixel($gd, $x0 + $sx, $y0, $py / $ed);
  264. } /* plot curve */
  265. $y0 += $sy;
  266. }
  267. if (2 * $ex > $dx && 2 * $fx <= $f + 2) { /* round y+ approximation pixel */
  268. if ($px < $ed) {
  269. $this->setPixel($gd, $x0, $y2 + $sy, $px / $ed);
  270. } /* plot curve */
  271. $x0 += $sx;
  272. }
  273. $xx = $x0;
  274. $x0 = $x3;
  275. $x3 = $xx;
  276. $sx = -$sx;
  277. $xb = -$xb; /* swap legs */
  278. $yy = $y0;
  279. $y0 = $y3;
  280. $y3 = $yy;
  281. $sy = -$sy;
  282. $yb = -$yb;
  283. $x1 = $x2;
  284. } while ($leg--); /* try other end */
  285. $this->plotLine($gd, $x0, $y0, $x3, $y3); /* remaining part in case of cusp or crunode */
  286. }
  287. protected function plotQuadSegment($gd, $x0, $y0, $x1, $y1, $x2, $y2)
  288. { /* draw an limited anti-aliased quadratic Bezier segment */
  289. $sx = $x2 - $x1;
  290. $sy = $y2 - $y1;
  291. $xx = $x0 - $x1;
  292. $yy = $y0 - $y1;
  293. $xy = $dx = $dy = $err = $ed = 0;
  294. $cur = $xx * $sy - $yy * $sx; /* $curvature */
  295. if ($sx * (int)$sx + $sy * (int)$sy > $xx * $xx + $yy * $yy) { /* begin with longer part */
  296. $x2 = $x0;
  297. $x0 = $sx + $x1;
  298. $y2 = $y0;
  299. $y0 = $sy + $y1;
  300. $cur = -$cur; /* swap P0 P2 */
  301. }
  302. if ($cur != 0) { /* no straight line */
  303. $xx += $sx;
  304. $xx *= $sx = $x0 < $x2 ? 1 : -1; /* x step direction */
  305. $yy += $sy;
  306. $yy *= $sy = $y0 < $y2 ? 1 : -1; /* y step direction */
  307. $xy = 2 * $xx * $yy;
  308. $xx *= $xx;
  309. $yy *= $yy; /* differences 2nd degree */
  310. if ($cur * $sx * $sy < 0) { /* negat$ed $curvature? */
  311. $xx = -$xx;
  312. $yy = -$yy;
  313. $xy = -$xy;
  314. $cur = -$cur;
  315. }
  316. $dx = 4.0 * $sy * ($x1 - $x0) * $cur + $xx - $xy; /* differences 1st degree */
  317. $dy = 4.0 * $sx * ($y0 - $y1) * $cur + $yy - $xy;
  318. $xx += $xx;
  319. $yy += $yy;
  320. $err = $dx + $dy + $xy; /* $error 1st step */
  321. do {
  322. $cur = min($dx + $xy, -$xy - $dy);
  323. $ed = max($dx + $xy, -$xy - $dy); /* approximate $error distance */
  324. $ed += 2 * $ed * $cur * $cur / (4 * $ed * $ed + $cur * $cur);
  325. $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy - $xy) / $ed); /* plot $curve */
  326. if ($x0 == $x2 || $y0 == $y2) {
  327. break;
  328. } /* $curve finish$ed */
  329. $x1 = $x0;
  330. $cur = $dx - $err;
  331. $y1 = 2 * $err + $dy < 0;
  332. if (2 * $err + $dx > 0) { /* x step */
  333. if ($err - $dy < $ed) {
  334. $this->setPixel($gd, $x0, $y0 + $sy, abs($err - $dy) / $ed);
  335. }
  336. $x0 += $sx;
  337. $dx -= $xy;
  338. $err += $dy += $yy;
  339. }
  340. if ($y1) { /* y step */
  341. if ($cur < $ed) {
  342. $this->setPixel($gd, $x1 + $sx, $y0, abs($cur) / $ed);
  343. }
  344. $y0 += $sy;
  345. $dy -= $xy;
  346. $err += $dx += $xx;
  347. }
  348. } while ($dy < $dx); /* gradient negates -> close curves */
  349. }
  350. $this->plotLine($gd, $x0, $y0, $x2, $y2); /* plot remaining needle to end */
  351. }
  352. protected function plotLine($gd, $x0, $y0, $x1, $y1)
  353. { /* draw a black (0) anti-aliased line on white (255) background */
  354. $dx = abs($x1 - $x0);
  355. $sx = $x0 < $x1 ? 1 : -1;
  356. $dy = -abs($y1 - $y0);
  357. $sy = $y0 < $y1 ? 1 : -1;
  358. $err = $dx + $dy;
  359. $e2 = $x2 = 0; /* $error value e_xy */
  360. $ed = $dx - $dy == 0 ? 1 : sqrt((float)$dx * $dx + (float)$dy * $dy);
  361. for (; ;) { /* pixel loop */
  362. $this->setPixel($gd, $x0, $y0, abs($err - $dx - $dy) / $ed);
  363. $e2 = $err;
  364. $x2 = $x0;
  365. if (2 * $e2 + $dx >= 0) { /* x step */
  366. if ($x0 == $x1) {
  367. break;
  368. }
  369. if ($e2 - $dy < $ed) {
  370. $this->setPixel($gd, $x0, $y0 + $sy, ($e2 - $dy) / $ed);
  371. }
  372. $err += $dy;
  373. $x0 += $sx;
  374. }
  375. if (2 * $e2 + $dy <= 0) { /* y step */
  376. if ($y0 == $y1) {
  377. break;
  378. }
  379. if ($dx - $e2 < $ed) {
  380. $this->setPixel($gd, $x2 + $sx, $y0, ($dx - $e2) / $ed);
  381. }
  382. $err += $dx;
  383. $y0 += $sy;
  384. }
  385. }
  386. }
  387. /**
  388. * @param resource $gd
  389. * @param int $x
  390. * @param int $y
  391. * @param float $ar Alpha ratio
  392. */
  393. protected function setPixel($gd, $x, $y, $ar)
  394. {
  395. list($r, $g, $b) = $this->color->getRgb();
  396. $c = imagecolorallocatealpha($gd, $r, $g, $b, 127 * $ar);
  397. imagesetpixel($gd, $x, $y, $c);
  398. }
  399. }