canvas_drawing_example_20150826.html
1 |
<!DOCTYPE html>
|
---|---|
2 |
<html lang="en"> |
3 |
<head>
|
4 |
<meta charset="utf-8" /> |
5 |
<title>Canvas Drawing Example</title> |
6 |
|
7 |
<script>
|
8 |
var canvas;
|
9 |
|
10 |
var ctx;
|
11 |
|
12 |
var pixel;
|
13 |
var pixData;
|
14 |
|
15 |
var rawColor;
|
16 |
|
17 |
function parseColor(spec)
|
18 |
{
|
19 |
if (typeof spec !== "string")
|
20 |
{
|
21 |
return null;
|
22 |
}
|
23 |
|
24 |
if (spec.length !== 7)
|
25 |
{
|
26 |
return null;
|
27 |
}
|
28 |
|
29 |
if (spec.charAt(0) !== "#")
|
30 |
{
|
31 |
return null;
|
32 |
}
|
33 |
|
34 |
var r = parseInt(spec.substr(1, 2), 16);
|
35 |
var g = parseInt(spec.substr(3, 2), 16);
|
36 |
var b = parseInt(spec.substr(5, 2), 16);
|
37 |
|
38 |
if (r === NaN || g === NaN || b === NaN)
|
39 |
{
|
40 |
return null;
|
41 |
}
|
42 |
|
43 |
return [ r, g, b ];
|
44 |
}
|
45 |
|
46 |
function validateColor(color)
|
47 |
{
|
48 |
if (!(color instanceof Array))
|
49 |
{
|
50 |
return false;
|
51 |
}
|
52 |
|
53 |
if (color.length !== 3)
|
54 |
{
|
55 |
return false;
|
56 |
}
|
57 |
|
58 |
if (typeof color[0] !== "number" ||
|
59 |
typeof color[1] !== "number" ||
|
60 |
typeof color[2] !== "number")
|
61 |
{
|
62 |
return false;
|
63 |
}
|
64 |
|
65 |
if (color[0] < 0 || color[0] > 255 ||
|
66 |
color[1] < 0 || color[1] > 255 ||
|
67 |
color[2] < 0 || color[2] > 255)
|
68 |
{
|
69 |
return false;
|
70 |
}
|
71 |
|
72 |
return true;
|
73 |
}
|
74 |
|
75 |
function createColorString(color)
|
76 |
{
|
77 |
if (!validateColor(color))
|
78 |
{
|
79 |
return null;
|
80 |
}
|
81 |
|
82 |
return "rgb(" + color[0].toString() + ", "
|
83 |
+ color[1].toString() + ", "
|
84 |
+ color[2].toString() + ")";
|
85 |
}
|
86 |
|
87 |
function setColor(ctx, color)
|
88 |
{
|
89 |
var colorSpec;
|
90 |
|
91 |
if (color instanceof Array)
|
92 |
{
|
93 |
if (validateColor(color))
|
94 |
{
|
95 |
colorSpec = createColorString(color);
|
96 |
|
97 |
// store the color in a form that can be used for direct drawing
|
98 |
rawColor = [ color[0], color[1], color[2] ];
|
99 |
}
|
100 |
else
|
101 |
{
|
102 |
// error
|
103 |
}
|
104 |
}
|
105 |
else
|
106 |
{
|
107 |
if (typeof color === "string")
|
108 |
{
|
109 |
colorSpec = color;
|
110 |
|
111 |
// store the color in a form that can be used for direct drawing
|
112 |
rawColor = parseColor(color);
|
113 |
}
|
114 |
else
|
115 |
{
|
116 |
// error
|
117 |
}
|
118 |
}
|
119 |
|
120 |
// change the canvas colors
|
121 |
ctx.fillStyle = colorSpec;
|
122 |
ctx.strokeStyle = colorSpec;
|
123 |
}
|
124 |
|
125 |
var SCALE_FACTOR = 0.7;
|
126 |
var MIN_COLOR_VALUE = Math.floor(1.0 / (1.0 - SCALE_FACTOR));
|
127 |
|
128 |
function lightenColor(color)
|
129 |
{
|
130 |
if (!validateColor(color))
|
131 |
{
|
132 |
return null;
|
133 |
}
|
134 |
|
135 |
// handle black directly, the result is NOT very much different but this is how it is
|
136 |
// done in Java
|
137 |
if (color[0] == 0 && color[1] == 0 && color[2] == 0)
|
138 |
return [ MIN_COLOR_VALUE, MIN_COLOR_VALUE, MIN_COLOR_VALUE ];
|
139 |
|
140 |
// force inputs to a minimim color value if between 0 and that minimum color value
|
141 |
// (yes, this is how Java does it)
|
142 |
var rIn = (color[0] > 0 && color[0] < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : color[0];
|
143 |
var gIn = (color[1] > 0 && color[1] < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : color[1];
|
144 |
var bIn = (color[2] > 0 && color[2] < MIN_COLOR_VALUE) ? MIN_COLOR_VALUE : color[2];
|
145 |
|
146 |
// scale it
|
147 |
var r = Math.min(Math.floor(rIn / SCALE_FACTOR), 255);
|
148 |
var g = Math.min(Math.floor(gIn / SCALE_FACTOR), 255);
|
149 |
var b = Math.min(Math.floor(bIn / SCALE_FACTOR), 255);
|
150 |
|
151 |
return [ r, g, b ];
|
152 |
}
|
153 |
|
154 |
function darkenColor(color)
|
155 |
{
|
156 |
if (!validateColor(color))
|
157 |
{
|
158 |
return null;
|
159 |
}
|
160 |
|
161 |
var r = Math.max(0, Math.floor(color[0] * SCALE_FACTOR));
|
162 |
var g = Math.max(0, Math.floor(color[1] * SCALE_FACTOR));
|
163 |
var b = Math.max(0, Math.floor(color[2] * SCALE_FACTOR));
|
164 |
|
165 |
return [ r, g, b ];
|
166 |
}
|
167 |
|
168 |
function drawPixel(ctx, x, y, color)
|
169 |
{
|
170 |
pixData[0] = color[0];
|
171 |
pixData[1] = color[1];
|
172 |
pixData[2] = color[2];
|
173 |
|
174 |
|
175 |
ctx.putImageData(pixel, x, y);
|
176 |
}
|
177 |
|
178 |
function rawLine(ctx, x1, y1, x2, y2, color)
|
179 |
{
|
180 |
var dx = Math.abs(x2 - x1);
|
181 |
var dy = Math.abs(y2 - y1);
|
182 |
|
183 |
var xIncr = x1 < x2 ? 1 : -1;
|
184 |
var yIncr = y1 < y2 ? 1 : -1;
|
185 |
|
186 |
dy = -dy;
|
187 |
|
188 |
var err = dx + dy;
|
189 |
var err2;
|
190 |
|
191 |
var x = x1;
|
192 |
var y = y1;
|
193 |
|
194 |
while (true)
|
195 |
{
|
196 |
drawPixel(ctx, x, y, color);
|
197 |
|
198 |
if (x == x2 && y == y2)
|
199 |
break;
|
200 |
|
201 |
err2 = err * 2;
|
202 |
|
203 |
if (err2 >= dy)
|
204 |
{
|
205 |
err += dy;
|
206 |
x += xIncr;
|
207 |
}
|
208 |
|
209 |
if (err2 <= dx)
|
210 |
{
|
211 |
err += dx;
|
212 |
y += yIncr;
|
213 |
}
|
214 |
}
|
215 |
}
|
216 |
|
217 |
function drawLine(x1, y1, x2, y2)
|
218 |
{
|
219 |
ctx.beginPath();
|
220 |
ctx.moveTo(x1, y1);
|
221 |
ctx.lineTo(x2, y2);
|
222 |
ctx.stroke();
|
223 |
}
|
224 |
|
225 |
function renderClosedPath(ctx, fill)
|
226 |
{
|
227 |
ctx.save();
|
228 |
ctx.clip();
|
229 |
|
230 |
if (fill)
|
231 |
{
|
232 |
ctx.fill();
|
233 |
}
|
234 |
else
|
235 |
{
|
236 |
ctx.stroke();
|
237 |
}
|
238 |
|
239 |
ctx.restore();
|
240 |
}
|
241 |
|
242 |
//
|
243 |
// Draw a rounded rectangle with the given context, dimensions and fill.
|
244 |
//
|
245 |
// @param {CanvasRenderingContext2D} ctx
|
246 |
// The canvas 2D graphics context.
|
247 |
// @param {Number} x
|
248 |
// X coordinate of the top left of the rectangle (if a square corner was placed
|
249 |
// there).
|
250 |
// @param {Number} y
|
251 |
// Y coordinate of the top left of the rectangle (if a square corner was placed
|
252 |
// there).
|
253 |
// @param {Number} width
|
254 |
// Width of the rectangle.
|
255 |
// @param {Number} height
|
256 |
// Height of the rectangle.
|
257 |
// @param {Number} diameter
|
258 |
// Diameter of the arc that defines the rounded corner.
|
259 |
// @param {Boolean} fill
|
260 |
// <code>true</code> to fill the rectangle with the current color, otherwise just
|
261 |
// stroke the rectangle.
|
262 |
//
|
263 |
function createRoundRect(ctx, x, y, width, height, diameter, fill)
|
264 |
{
|
265 |
var radius = diameter / 2;
|
266 |
|
267 |
// arc control points
|
268 |
var northWestX = x;
|
269 |
var northWestY = y;
|
270 |
var northEastX = x + width;
|
271 |
var northEastY = y;
|
272 |
var southWestX = x;
|
273 |
var southWestY = y + height;
|
274 |
var southEastX = x + width;
|
275 |
var southEastY = y + height;
|
276 |
|
277 |
// line end points
|
278 |
var topLeftX = northWestX + radius;
|
279 |
var topLeftY = northWestY;
|
280 |
var topRightX = northEastX - radius;
|
281 |
var topRightY = northEastY;
|
282 |
var rightUpX = northEastX;
|
283 |
var rightUpY = northEastY + radius;
|
284 |
var rightDownX = southEastX;
|
285 |
var rightDownY = southEastY - radius;
|
286 |
var bottomRightX = southEastX - radius;
|
287 |
var bottomRightY = southEastY;
|
288 |
var bottomLeftX = southWestX + radius;
|
289 |
var bottomLeftY = southWestY;
|
290 |
var leftDownX = southWestX;
|
291 |
var leftDownY = southWestY - radius;
|
292 |
var leftUpX = northWestX;
|
293 |
var leftUpY = northWestY + radius;
|
294 |
|
295 |
ctx.beginPath();
|
296 |
ctx.moveTo(topRightX, topRightY);
|
297 |
ctx.quadraticCurveTo(northEastX, northEastY, rightUpX, rightUpY);
|
298 |
ctx.lineTo(rightDownX, rightDownY);
|
299 |
ctx.quadraticCurveTo(southEastX, southEastY, bottomRightX, bottomRightY);
|
300 |
ctx.lineTo(bottomLeftX, bottomLeftY);
|
301 |
ctx.quadraticCurveTo(southWestX, southWestY, leftDownX, leftDownY);
|
302 |
ctx.lineTo(leftUpX, leftUpY);
|
303 |
ctx.quadraticCurveTo(northWestX, northWestY, topLeftX, topLeftY);
|
304 |
ctx.closePath();
|
305 |
|
306 |
renderClosedPath(ctx, fill);
|
307 |
|
308 |
// overdraw the anti-aliased line segments
|
309 |
rawLine(ctx, topLeftX, topLeftY, topRightX, topRightY, rawColor);
|
310 |
rawLine(ctx, rightUpX, rightUpY, rightDownX, rightDownY, rawColor);
|
311 |
rawLine(ctx, bottomLeftX, bottomLeftY, bottomRightX, bottomRightY, rawColor);
|
312 |
rawLine(ctx, leftUpX, leftUpY, leftDownX, leftDownY, rawColor);
|
313 |
}
|
314 |
|
315 |
function create3DRect(ctx, x, y, width, height, fill, raised)
|
316 |
{
|
317 |
// save off our colors
|
318 |
var oriFillColor = ctx.fillStyle;
|
319 |
var oriStrokeColor = ctx.strokeStyle;
|
320 |
|
321 |
// both colors should always be assigned the same value on entry to any drawing op
|
322 |
// so it should be safe to use just the fill color
|
323 |
var color = parseColor(oriFillColor);
|
324 |
|
325 |
if (color === null)
|
326 |
{
|
327 |
return;
|
328 |
}
|
329 |
|
330 |
var darker = darkenColor(color);
|
331 |
var lighter = lightenColor(color);
|
332 |
|
333 |
// save off our state so that we can clear our clipping region on exit
|
334 |
ctx.save();
|
335 |
|
336 |
// the Java implementation draws 1 pixel larger in each dimension
|
337 |
width = width + 1;
|
338 |
height = height + 1;
|
339 |
|
340 |
// setup our clipping region, this is needed because the line width (1 pixel wide) is
|
341 |
// drawn half on one side of hte path and half on the other side; this makes paths a
|
342 |
// half pixel bigger than the caller would expect; by defining the same path as a
|
343 |
// clipping region, the half pixel width that is outside the path is never output
|
344 |
ctx.beginPath();
|
345 |
ctx.rect(x, y, width, height);
|
346 |
ctx.clip();
|
347 |
|
348 |
var current = raised ? color : darker;
|
349 |
|
350 |
// fill the rectangle with the original color (raised mode) or the darker color otherwise
|
351 |
if (fill)
|
352 |
{
|
353 |
ctx.fillStyle = createColorString(current);
|
354 |
ctx.beginPath();
|
355 |
ctx.rect(x, y, width, height);
|
356 |
renderClosedPath(ctx, fill);
|
357 |
}
|
358 |
|
359 |
current = raised ? lighter : darker;
|
360 |
|
361 |
// draw the top and left sides in the contrasting color (raised mode) or the darker color
|
362 |
// otherwise
|
363 |
rawLine(ctx, x, y, x + width - 1, y, current);
|
364 |
rawLine(ctx, x, y + 1, x, y + height, current);
|
365 |
|
366 |
// draw the bottom and right sides in the darker color (raised mode) or the contrasting
|
367 |
// color otherwise
|
368 |
current = raised ? darker : lighter;
|
369 |
rawLine(ctx, x + 1, y + height, x + width, y + height, current);
|
370 |
rawLine(ctx, x + width, y, x + width, y + height - 1, current);
|
371 |
|
372 |
// clear the clipping region
|
373 |
ctx.restore();
|
374 |
|
375 |
// reset our colors
|
376 |
ctx.fillStyle = oriFillColor;
|
377 |
ctx.strokeStyle = oriStrokeColor;
|
378 |
}
|
379 |
|
380 |
function createRect(ctx, x, y, width, height, color, fill)
|
381 |
{
|
382 |
// use vector operations for the interior of the rectangle
|
383 |
if (fill)
|
384 |
{
|
385 |
ctx.beginPath();
|
386 |
ctx.rect(x, y, width, height);
|
387 |
renderClosedPath(ctx, fill);
|
388 |
}
|
389 |
|
390 |
// now overdraw the stroked portion
|
391 |
rawLine(ctx, x, y, x + width, y, color);
|
392 |
rawLine(ctx, x, y + 1, x, y + height, color);
|
393 |
rawLine(ctx, x + width, y + 1, x + width, y + height, color);
|
394 |
rawLine(ctx, x + 1, y + height, x + width - 1, y + height, color);
|
395 |
}
|
396 |
|
397 |
function drawPolygon(ctx, xPoints, yPoints, num, fill)
|
398 |
{
|
399 |
var i;
|
400 |
|
401 |
// use vector operations for the interior of the poygon
|
402 |
if (fill)
|
403 |
{
|
404 |
ctx.beginPath();
|
405 |
ctx.moveTo(xPoints[0], yPoints[0]);
|
406 |
for (i = 1; i < num; i++)
|
407 |
{
|
408 |
ctx.lineTo(xPoints[i], yPoints[i]);
|
409 |
}
|
410 |
renderClosedPath(ctx, fill);
|
411 |
}
|
412 |
|
413 |
// now overdraw the stroked portion
|
414 |
for (i = 0; i < (num - 1); i++)
|
415 |
{
|
416 |
rawLine(ctx, xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1], rawColor);
|
417 |
}
|
418 |
}
|
419 |
|
420 |
window.onload=function()
|
421 |
{
|
422 |
canvas = document.getElementById("bogus");
|
423 |
ctx = canvas.getContext('2d');
|
424 |
|
425 |
canvas.top = 150;
|
426 |
canvas.left = 150;
|
427 |
canvas.width = 600;
|
428 |
canvas.height = 600;
|
429 |
|
430 |
pixel = ctx.createImageData(1, 1);
|
431 |
pixData = pixel.data;
|
432 |
pixData[3] = 0xFF; // force alpha to 100% opaque
|
433 |
|
434 |
// ctx.globalCompositeOperation = "source-over";
|
435 |
ctx.lineWidth = 1;
|
436 |
ctx.imageSmoothingEnabled = false;
|
437 |
ctx.translate(0.5, 0.5);
|
438 |
|
439 |
// draw background
|
440 |
// canvas.style.backgroundColor = "#EEEEEE";
|
441 |
// canvas.style.backgroundColor = "rbga(238, 238, 238, 1.0)";
|
442 |
setColor(ctx, "#EEEEEE");
|
443 |
createRect(ctx, 0, 0, canvas.width, canvas.height, rawColor, true);
|
444 |
|
445 |
// reset black as default color
|
446 |
setColor(ctx, "#000000");
|
447 |
|
448 |
var t;
|
449 |
|
450 |
// draw Y axis tick marks
|
451 |
for (t = 0; t < canvas.height; t++)
|
452 |
{
|
453 |
// major every 10 pixels
|
454 |
if ((t % 10) == 0)
|
455 |
{
|
456 |
rawLine(ctx, 0, t, 5, t, rawColor);
|
457 |
}
|
458 |
// minor every 2 pixels
|
459 |
if ((t % 2) == 0)
|
460 |
{
|
461 |
rawLine(ctx, 0, t, 2, t, rawColor);
|
462 |
}
|
463 |
}
|
464 |
|
465 |
// draw X axis tick marks (can't start at 0 because the Y ticks are there)
|
466 |
for (t = 10; t < canvas.width; t++)
|
467 |
{
|
468 |
// major every 10 pixels
|
469 |
if ((t % 10) == 0)
|
470 |
{
|
471 |
rawLine(ctx, t, 0, t, 5, rawColor);
|
472 |
}
|
473 |
// minor every 2 pixels
|
474 |
if ((t % 2) == 0)
|
475 |
{
|
476 |
rawLine(ctx, t, 0, t, 2, rawColor);
|
477 |
}
|
478 |
}
|
479 |
|
480 |
rawLine(ctx, 10, 10, 590, 30, rawColor);
|
481 |
|
482 |
createRect(ctx, 40, 40, 55, 30, rawColor, false);
|
483 |
createRect(ctx, 150, 40, 55, 30, rawColor, true);
|
484 |
|
485 |
createRoundRect(ctx, 40, 90, 55, 30, 5, false);
|
486 |
createRoundRect(ctx, 40, 130, 55, 30, 5, true);
|
487 |
createRoundRect(ctx, 110, 90, 55, 30, 8, false);
|
488 |
createRoundRect(ctx, 110, 130, 55, 30, 8, true);
|
489 |
createRoundRect(ctx, 180, 90, 55, 30, 10, false);
|
490 |
createRoundRect(ctx, 180, 130, 55, 30, 10, true);
|
491 |
createRoundRect(ctx, 250, 90, 55, 30, 15, false);
|
492 |
createRoundRect(ctx, 250, 130, 55, 30, 15, true);
|
493 |
createRoundRect(ctx, 320, 90, 55, 30, 20, false);
|
494 |
createRoundRect(ctx, 320, 130, 55, 30, 20, true);
|
495 |
createRoundRect(ctx, 390, 90, 55, 30, 25, false);
|
496 |
createRoundRect(ctx, 390, 130, 55, 30, 25, true);
|
497 |
createRoundRect(ctx, 460, 90, 55, 30, 30, false);
|
498 |
createRoundRect(ctx, 460, 130, 55, 30, 30, true);
|
499 |
|
500 |
// setColor(ctx, "#00FF00");
|
501 |
setColor(ctx, "#99B4D1");
|
502 |
create3DRect(ctx, 40, 175, 75, 30, false, true);
|
503 |
create3DRect(ctx, 130, 175, 150, 60, false, true);
|
504 |
create3DRect(ctx, 295, 175, 75, 30, true, true);
|
505 |
create3DRect(ctx, 395, 175, 150, 60, true, true);
|
506 |
create3DRect(ctx, 40, 250, 75, 30, false, false);
|
507 |
create3DRect(ctx, 130, 250, 150, 60, false, false);
|
508 |
create3DRect(ctx, 295, 250, 75, 30, true, false);
|
509 |
create3DRect(ctx, 395, 250, 150, 60, true, false);
|
510 |
setColor(ctx, "#000000");
|
511 |
create3DRect(ctx, 40, 325, 75, 30, false, false);
|
512 |
create3DRect(ctx, 130, 325, 150, 60, false, false);
|
513 |
create3DRect(ctx, 295, 325, 75, 30, true, false);
|
514 |
create3DRect(ctx, 395, 325, 150, 60, true, false);
|
515 |
|
516 |
var xPoints = [ 40, 80, 40 ];
|
517 |
var yPoints = [ 400, 420, 440 ];
|
518 |
drawPolygon(ctx, xPoints, yPoints, 3, true);
|
519 |
|
520 |
xPoints = [ 140, 100, 140 ];
|
521 |
yPoints = [ 440, 420, 400 ];
|
522 |
drawPolygon(ctx, xPoints, yPoints, 3, true);
|
523 |
|
524 |
xPoints = [ 160, 180, 200 ];
|
525 |
yPoints = [ 400, 440, 400 ];
|
526 |
drawPolygon(ctx, xPoints, yPoints, 3, true);
|
527 |
|
528 |
xPoints = [ 220, 240, 260 ];
|
529 |
yPoints = [ 440, 400, 440 ];
|
530 |
drawPolygon(ctx, xPoints, yPoints, 3, true);
|
531 |
|
532 |
xPoints = [ 280, 300, 320, 310, 290 ];
|
533 |
yPoints = [ 420, 440, 420, 400, 400 ];
|
534 |
drawPolygon(ctx, xPoints, yPoints, 5, true);
|
535 |
|
536 |
xPoints = [ 340, 360, 380, 370, 350 ];
|
537 |
yPoints = [ 420, 400, 420, 440, 440 ];
|
538 |
drawPolygon(ctx, xPoints, yPoints, 5, true);
|
539 |
};
|
540 |
</script>
|
541 |
</head>
|
542 |
<body>
|
543 |
<canvas id="bogus" style="position:'absolute';">No HTML5 support in your browser!</canvas> |
544 |
</body>
|
545 |
</html>
|