1<?php 2/** 3 * SketchCanvas server-side renderer 4 * 5 * It requires GD and Yaml extensions set up for PHP in order to work. 6 */ 7 8define('FONTFACE', "NotoSansCJKjp-Regular.otf"); 9 10require_once "spyc.php"; 11 12$size = Array(640, 480); 13$drawdata = null; 14 15try{ 16 if(isset($_GET['fname']) || isset($_POST['drawdata']) || isset($_GET['drawdata'])){ 17 if(isset($_GET['fname'])) 18 $drawdata = Spyc::YAMLLoad('data/' . $_GET['fname']); 19 else if(isset($_POST['drawdata'])) 20 $drawdata = Spyc::YAMLLoadString($_POST['drawdata']); 21 else 22 $drawdata = Spyc::YAMLLoadString($_GET['drawdata']); 23 foreach ($drawdata as $key => $value) { 24 switch($value["type"]){ 25 case "meta": 26 $size = $value["size"]; 27 break; 28 } 29 } 30 } 31} 32catch(Exception $e){ 33 echo "failed\n"; 34 echo $e->getMessage(); 35 return; 36} 37 38$im = imagecreatetruecolor($size[0], $size[1]); 39 40$white = imagecolorallocate($im, 255, 255, 255); 41$c = imagecolorallocate($im, 255, 0, 0); 42 43imagefilledrectangle($im, 0, 0, $size[0], $size[1], $white); 44 45function pixelToPoint($px){ 46 return $px * 0.525; 47} 48 49/// @brief Dot product 45 degrees 50/// @param a vector 51/// @param b length 52function l_vec($a, $b) { 53 $ax = $a[1][0]-$a[0][0]; 54 $ay = $a[1][1]-$a[0][1]; 55 $rate = $b / sqrt($ax * $ax + $ay * $ay); 56 $ax *= $rate; 57 $ay *= $rate; 58 $rad1 = pi() / 4.0; // 45° 59 $rad2 = -pi() / 4.0; // -45° 60 $a1x = $ax * cos($rad1) - $ay * sin($rad1); 61 $a1y = $ax * sin($rad1) + $ay * cos($rad1); 62 $a2x = $ax * cos($rad2) - $ay * sin($rad2); 63 $a2y = $ax * sin($rad2) + $ay * cos($rad2); 64 $c = array(2); 65 $c[0] = array( $a1x, $a1y ); 66 $c[1] = array( $a2x, $a2y ); 67 return $c; 68} 69 70/// @brief Dot product 90 degrees 71/// @param a vector 72/// @param b length 73function l_vec9($a, $b) { 74 $ax = $a[1][0]-$a[0][0]; 75 $ay = $a[1][1]-$a[0][1]; 76 $rate = $b / sqrt($ax * $ax + $ay * $ay); 77 $ax *= $rate; 78 $ay *= $rate; 79 $a1x = -$ay; 80 $a1y = $ax; 81 $a2x = $ay; 82 $a2y = -$ax; 83 $c = array(2); 84 $c[0] = array( $a1x, $a1y ); 85 $c[1] = array( $a2x, $a2y ); 86 return $c; 87} 88 89function l_arrow($im, $arr, $color) { 90 $c = l_vec($arr, 6); 91 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$c[0][0], $arr[1][1]-$c[0][1], $color); 92 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$c[1][0], $arr[1][1]-$c[1][1], $color); 93} 94 95// draw double arrow 96function l_darrow($im, $arr, $color) { 97 $c = l_vec9($arr, 2); 98 $d = l_vec($arr, 8); 99 imageline($im, $arr[0][0]+$c[0][0], $arr[0][1]+$c[0][1], $arr[1][0]+$c[0][0], $arr[1][1]+$c[0][1], $color); 100 imageline($im, $arr[0][0]+$c[0][0], $arr[0][1]+$c[0][1], $arr[1][0]+$c[0][0], $arr[1][1]+$c[0][1], $color); 101 imageline($im, $arr[0][0]+$c[1][0], $arr[0][1]+$c[1][1], $arr[1][0]+$c[1][0], $arr[1][1]+$c[1][1], $color); 102 imageline($im, $arr[0][0]+$c[1][0], $arr[0][1]+$c[1][1], $arr[1][0]+$c[1][0], $arr[1][1]+$c[1][1], $color); 103 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$d[0][0], $arr[1][1]-$d[0][1], $color); 104 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$d[1][0], $arr[1][1]-$d[1][1], $color); 105} 106 107// draw twin arrow 108function l_tarrow($im, $arr, $color) { 109 $c = l_vec($arr, 6); 110 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$c[0][0], $arr[1][1]-$c[0][1], $color); 111 imageline($im, $arr[1][0], $arr[1][1], $arr[1][0]-$c[1][0], $arr[1][1]-$c[1][1], $color); 112 $a = array($arr[1], $arr[0]); 113 $c = l_vec($a, 6); 114 imageline($im, $arr[0][0], $arr[0][1], $arr[0][0]-$c[0][0], $arr[0][1]-$c[0][1], $color); 115 imageline($im, $arr[0][0], $arr[0][1], $arr[0][0]-$c[1][0], $arr[0][1]-$c[1][1], $color); 116} 117 118function l_hige($im, $arr, $color) { 119 $c = l_vec($arr, 6); 120 imageline($im, $arr[1][0]-$c[0][0], $arr[1][1]-$c[0][1], $arr[1][0], $arr[1][1], $color); 121 imageline($im, $arr[1][0]-$c[1][0], $arr[1][1]-$c[1][1], $arr[1][0], $arr[1][1], $color); 122} 123 124// draw star 125function l_star($im, $arr, $color) { 126 $x = $arr[0][0]; 127 $y = $arr[0][1]; 128 imagepolygon($im, array( 129 $x+8, $y-3, 130 $x+14, $y+13, 131 $x, $y+2, 132 $x+16, $y+2, 133 $x+2, $y+13), 134 5, $color); 135} 136 137// draw check 138function l_check($im, $arr, $color) { 139 $x = $arr[0][0]; 140 $y = $arr[0][1]; 141 imageline($im, $x, $y, $x+5, $y+7, $color); 142 imageline($im, $x+5, $y+7, $x+20, $y, $color); 143} 144 145// draw complete 146function l_complete($im, $arr, $color) { 147 imagettftext($im, pixelToPoint(20), 0, $arr[0][0]+3, $arr[0][1]+10, $c, "/usr/share/fonts/vlgothic/VL-Gothic-Regular.ttf", '済'); 148 imageellipse($im, $arr[0][0]+9, $arr[0][1]+5, 16, 16, $color); 149} 150 151function colorSelect($im, $value){ 152 $c = null; 153 if(isset($value['color'])){ 154 switch($value['color']){ 155 default: 156 case 'black': 157 $c = imagecolorallocate($im, 0, 0, 0); 158 break; 159 case 'blue': 160 $c = imagecolorallocate($im, 0, 0, 255); 161 break; 162 case 'red': 163 $c = imagecolorallocate($im, 255, 0, 0); 164 break; 165 case 'green': 166 $c = imagecolorallocate($im, 0, 255, 0); 167 break; 168 } 169 } 170 else 171 $c = imagecolorallocate($im, 0, 0, 0); 172 return $c; 173} 174 175/// Length of 2-D vector 176function veclen($a){ 177 return sqrt($a[0] * $a[0] + $a[1] * $a[1]); 178} 179 180/// Subtraction of 2-D vectors 181function vecsub($a, $b){ 182 return array($a[0] - $b[0], $a[1] - $b[1]); 183} 184 185/// Distance of 2-D vectors 186function vecdist($a, $b){ 187 return veclen(vecsub($a, $b)); 188} 189 190/// Returns dividing point of line between $a and $b that divide 191/// the line by $t and 1 - $t. 192function midPoint($a, $b, $t){ 193 return array($a[0] * (1. - $t) + $b[0] * $t, 194 $a[1] * (1. - $t) + $b[1] * $t); 195} 196 197/// Obtain coordinates of a point on a quadratic bezier curve. 198function quadraticBezierPoint($a, $b, $c, $t){ 199 $ab = midPoint($a, $b, $t); 200 $bc = midPoint($b, $c, $t); 201 return midPoint($ab, $bc, $t); 202} 203 204/// Obtain coordinates of a point on a cubic bezier curve. 205function cubicBezierPoint($a, $c, $d, $b, $t){ 206 $ac = midPoint($a, $c, $t); 207 $cd = midPoint($c, $d, $t); 208 $db = midPoint($d, $b, $t); 209 $acd = midPoint($ac, $cd, $t); 210 $cdb = midPoint($cd, $db, $t); 211 return midPoint($acd, $cdb, $t); 212 213} 214 215/// Draws a cubic Bezier curve on image $im. 216function cubicBezierCurve($im, $prev, $p, $color){ 217 $a = array($prev["x"], $prev["y"]); 218 $b = array($p["x"], $p["y"]); 219 $c = isset($p["cx"]) && isset($p["cy"]) ? array($p["cx"], $p["cy"]) : $a; 220 $d = isset($p["dx"]) && isset($p["dy"]) ? array($p["dx"], $p["dy"]) : $b; 221 222 // If both c and d control points are not defined, just draw a straight line, 223 if((!isset($p["cx"]) || !isset($p["cy"])) && (!isset($p["dx"]) || !isset($p["dy"]))){ 224 imageline($im, $a[0], $a[1], $b[0], $b[1], $color); 225 return; 226 } 227 228 // The curve is actually made of line segments. 229 // We determine the number of segments here by first measuring 230 // the rough length of the whole shape. 231 $length = vecdist($a, $c) 232 + vecdist($c, $d) 233 + vecdist($d, $b); 234 $divs = ceil($length / 10.); // The divisor determines how smooth the curve is. 235 236 $a0 = $a; 237 238 for($t = 0; $t <= $divs; $t++){ 239 $p1 = cubicBezierPoint($a, $c, $d, $b, $t / $divs); 240 imageline($im, $a0[0], $a0[1], $p1[0], $p1[1], $color); 241 $a0 = $p1; 242 } 243} 244 245try{ 246 if($drawdata !== null){ 247 require_once "lib.php"; 248 foreach ($drawdata as $key => $value) { 249 $c = colorSelect($im, $value); 250 switch($value["type"]){ 251 case "line": 252 case "arrow": 253 case "barrow": 254 case "darrow": 255 case "rect": 256 case "rectfill": 257 case "ellipse": 258 case "ellipsefill": 259 $pts = parsePointList($value["points"]); 260 $p1 = $pts[0]; 261 $p2 = $pts[1]; 262 $wid = isset($value["width"]) ? $value["width"] : 1; 263 imagesetthickness($im, $wid); 264 imageantialias($im, $wid === 1); 265 if($value["type"] === "rect") 266 imagerectangle($im, $p1[0], $p1[1], $p2[0], $p2[1], $c); 267 else if($value["type"] === "rectfill") 268 imagefilledrectangle($im, $p1[0], $p1[1], $p2[0], $p2[1], $c); 269 else if($value["type"] === "ellipse") 270 imageellipse($im, ($p1[0] + $p2[0]) / 2, ($p1[1] + $p2[1]) / 2, abs($p2[0] - $p1[0]), abs($p2[1] - $p1[1]), $c); 271 else if($value["type"] === "ellipsefill") 272 imagefilledellipse($im, ($p1[0] + $p2[0]) / 2, ($p1[1] + $p2[1]) / 2, abs($p2[0] - $p1[0]), abs($p2[1] - $p1[1]), $c); 273 else if($value["type"] === "darrow") 274 l_darrow($im, $pts, $c); 275 else 276 imageline($im, $p1[0], $p1[1], $p2[0], $p2[1], $c); 277 if($value["type"] === "arrow") 278 l_arrow($im, $pts, $c); 279 else if($value["type"] === "barrow") 280 l_tarrow($im, $pts, $c); 281 break; 282 case 'arc': 283 case 'arcarrow': 284 case 'arcbarrow': 285 // We emulate HTML5 Canvas's Context2D.quadraticCurveTo(), 286 // which is really an implementation of quadratic Bezier curve. 287 // Bezier curves are very easy to implement. See: 288 // https://en.wikipedia.org/wiki/B%C3%A9zier_curve 289 $pts = parsePointList($value["points"]); 290 291 // The curve is actually made of line segments. 292 // We determine the number of segments here by first measuring 293 // the rough length of the whole shape. 294 $length = vecdist($pts[0], $pts[1]) 295 + vecdist($pts[1], $pts[2]); 296 $divs = ceil($length / 20.); 297 $prev = $pts[0]; 298 299 $wid = isset($value["width"]) ? $value["width"] : 1; 300 imagesetthickness($im, $wid); 301 imageantialias($im, $wid === 1); 302 for($t = 0; $t <= $divs; $t++){ 303 $p1 = quadraticBezierPoint($pts[0], $pts[1], $pts[2], $t / $divs); 304 imageline($im, $prev[0], $prev[1], $p1[0], $p1[1], $c); 305 $prev = $p1; 306 } 307 if($value["type"] === "arcarrow" || $value["type"] === "arcbarrow") 308 l_hige($im, array($pts[1], $pts[2]), $c); 309 if($value["type"] === "arcbarrow") 310 l_hige($im, array($pts[1], $pts[0]), $c); 311 break; 312 case 'star': 313 l_star($im, parsePointList($value["points"]), $c); 314 break; 315 case 'check': 316 l_check($im, parsePointList($value["points"]), $c); 317 break; 318 case 'done': 319 l_complete($im, parsePointList($value["points"]), $c); 320 break; 321 case 'text': 322 $pts = parsePointList($value["points"]); 323 $fontsize = 10; 324 if (1 == $value["width"]) $fontsize = 14; 325 else if (2 == $value["width"]) $fontsize = 16; 326 else $fontsize = 20; 327 imagettftext($im, pixelToPoint($fontsize), 0, $pts[0][0], $pts[0][1], $c, FONTFACE, $value["text"]); 328 break; 329 case 'path': 330 $pts = parsePathCommands($value["d"]); 331 $wid = isset($value["width"]) ? $value["width"] : 1; 332 imagesetthickness($im, $wid); 333 imageantialias($im, $wid === 1); 334 $prev = null; 335 foreach($pts as $i => $p){ 336 if($prev !== null) 337 cubicBezierCurve($im, $prev, $p, $c); 338 $prev = $p; 339 } 340 if(isset($value["arrow"]) && 1 < count($pts)){ 341 $set = seq2set($value["arrow"]); 342 if(isset($set["head"])){ 343 $first = $pts[0]; 344 $first2 = $pts[1]; 345 $a = array(); 346 if(isset($first2["cx"]) && isset($first2["cy"]) && ($first2["cx"] !== $first["x"] || $first2["cy"] !== $first["y"])) 347 $a[] = array($first2["cx"], $first2["cy"]); 348 else if(isset($first2["dx"]) && isset($first2["dy"]) && ($first2["dx"] !== $first["x"] || $first2["dy"] !== $first["y"])) 349 $a[] = array($first2["dx"], $first2["dy"]); 350 else 351 $a[] = array($first2["x"], $first2["y"]); 352 $a[] = array($first["x"], $first["y"]); 353 l_hige($im, $a, $c); 354 } 355 if(isset($set["tail"])){ 356 $last = $pts[count($pts)-1]; 357 $last2 = $pts[count($pts)-2]; 358 $a = array(); 359 if(isset($last["dx"]) && isset($last["dy"]) && ($last["dx"] !== $last["x"] || $last["dy"] !== $last["y"])) 360 $a[0] = array($last["dx"], $last["dy"]); 361 else if(isset($last["cx"]) && isset($last["cy"]) && ($last["cx"] !== $last["x"] || $last["cy"] !== $last["y"])) 362 $a[0] = array($last["cx"], $last["cy"]); 363 else 364 $a[0] = array($last2["x"], $last2["y"]); 365 $a[1] = array($last["x"], $last["y"]); 366 l_hige($im, $a, $c); 367 } 368 } 369 break; 370 } 371 } 372 } 373} 374catch(Exception $e){ 375 echo "failed\n"; 376 echo $e->getMessage(); 377 return; 378} 379 380$useBase64 = false; 381if(isset($_GET['base64']) || isset($_POST['base64'])) 382 $useBase64 = true; 383else 384 header('Content-Type: image/png'); 385 386if($useBase64) 387 ob_start(); 388imagepng($im); 389imagedestroy($im); 390if($useBase64){ 391 $imagedata = ob_get_contents(); 392 ob_end_clean(); 393 print base64_encode($imagedata); 394} 395