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