Project

General

Profile

canvas_drawing_example_20150826.html

Greg Shah, 08/26/2015 06:47 PM

Download (19.6 KB)

 
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 inset = 0;
349
            
350
            var current = raised ? color : darker;
351
            
352
            // fill the rectangle with the original color (raised mode) or the darker color otherwise
353
            if (fill)
354
            {
355
               inset = 1;
356
               ctx.fillStyle = createColorString(current);
357
               ctx.beginPath();
358
               ctx.rect(x, y, width - 2, height - 2);
359
               renderClosedPath(ctx, fill);
360
            }
361
            
362
            current = raised ? lighter : darker;
363
            
364
            // draw the top and left sides in the contrasting color (raised mode) or the darker color
365
            // otherwise
366
            rawLine(ctx, x, y, x + width - 2 - inset, y, current);
367
            rawLine(ctx, x, y + 1, x, y + height - 1 - inset, current);
368
            
369
            // draw the bottom and right sides in the darker color (raised mode) or the contrasting
370
            // color otherwise
371
            current = raised ? darker : lighter;
372
            rawLine(ctx, x + 1, y + height - 1 - inset, x + width - 1 - inset, y + height - 1 - inset, current);
373
            rawLine(ctx, x + width - 1 - inset, y, x + width - 1 - inset, y + height - 2, current);
374
            
375
            // clear the clipping region
376
            ctx.restore();
377
            
378
            // reset our colors
379
            ctx.fillStyle   = oriFillColor;
380
            ctx.strokeStyle = oriStrokeColor;
381
         }                         
382
         
383
         function createRect(ctx, x, y, width, height, color, fill)
384
         {
385
            var inset = 0;
386
         
387
            // use vector operations for the interior of the rectangle
388
            if (fill)
389
            {
390
               inset = 1;
391
               ctx.beginPath();
392
               ctx.rect(x, y, width - inset, height - 1);
393
               renderClosedPath(ctx, fill);
394
            }
395
            
396
            // now overdraw the stroked portion
397
            rawLine(ctx, x, y, x + width - inset, y, color);
398
            rawLine(ctx, x, y + 1, x, y + height - inset, color);
399
            rawLine(ctx, x + width - inset, y + 1, x + width - inset, y + height - inset, color);
400
            rawLine(ctx, x + 1, y + height - inset, x + width - 1 - inset, y + height - inset, color);
401
         }                         
402
         
403
         function drawPolygon(ctx, xPoints, yPoints, num, fill)
404
         {
405
            var i;
406
         
407
            // use vector operations for the interior of the poygon
408
            if (fill)
409
            {
410
               ctx.beginPath();
411
               ctx.moveTo(xPoints[0], yPoints[0]);
412
               for (i = 1; i < num; i++)
413
               {
414
                  ctx.lineTo(xPoints[i], yPoints[i]);
415
               }
416
               renderClosedPath(ctx, fill);
417
            }
418
            
419
            // now overdraw the stroked portion
420
            for (i = 0; i < (num - 1); i++)
421
            {
422
               rawLine(ctx, xPoints[i], yPoints[i], xPoints[i + 1], yPoints[i + 1], rawColor);
423
            }
424
         }                         
425
         
426
         window.onload=function()
427
         {
428
            canvas = document.getElementById("bogus");
429
            ctx = canvas.getContext('2d');
430
            
431
            canvas.top = 150;
432
            canvas.left = 150;
433
            canvas.width = 600;
434
            canvas.height = 600;
435
            
436
            pixel   = ctx.createImageData(1, 1);
437
            pixData = pixel.data;
438
            pixData[3] = 0xFF; // force alpha to 100% opaque
439
            
440
            // ctx.globalCompositeOperation = "source-over";
441
            ctx.lineWidth = 1;
442
            ctx.imageSmoothingEnabled = false;
443
            ctx.translate(0.5, 0.5);
444
            
445
            // draw background
446
            // canvas.style.backgroundColor = "#EEEEEE";
447
            // canvas.style.backgroundColor = "rbga(238, 238, 238, 1.0)";
448
            setColor(ctx, "#EEEEEE");
449
            createRect(ctx, 0, 0, canvas.width, canvas.height, rawColor, true);
450
            
451
            // reset black as default color
452
            setColor(ctx, "#000000");
453
            
454
            var t;
455
            
456
            // draw Y axis tick marks
457
            for (t = 0; t < canvas.height; t++)
458
            {
459
               // major every 10 pixels
460
               if ((t % 10) == 0)
461
               {
462
                  rawLine(ctx, 0, t, 5, t, rawColor);
463
               }
464
               // minor every 2 pixels
465
               if ((t % 2) == 0)
466
               {
467
                  rawLine(ctx, 0, t, 2, t, rawColor);
468
               }
469
            }
470
            
471
            // draw X axis tick marks (can't start at 0 because the Y ticks are there)
472
            for (t = 10; t < canvas.width; t++)
473
            {
474
               // major every 10 pixels
475
               if ((t % 10) == 0)
476
               {
477
                  rawLine(ctx, t, 0, t, 5, rawColor);
478
               }
479
               // minor every 2 pixels
480
               if ((t % 2) == 0)
481
               {
482
                  rawLine(ctx, t, 0, t, 2, rawColor);
483
               }
484
            }
485
            
486
            rawLine(ctx, 10, 10, 590, 30, rawColor);
487
         
488
            createRect(ctx, 40, 40, 55, 30, rawColor, false);
489
            createRect(ctx, 150, 40, 55, 30, rawColor, true);
490
            
491
            createRoundRect(ctx, 40, 90, 55, 30, 5,    false);
492
            createRoundRect(ctx, 40, 130, 55, 30, 5,   true);
493
            createRoundRect(ctx, 110, 90, 55, 30, 8,   false);
494
            createRoundRect(ctx, 110, 130, 55, 30, 8,  true);
495
            createRoundRect(ctx, 180, 90, 55, 30, 10,  false);
496
            createRoundRect(ctx, 180, 130, 55, 30, 10, true);
497
            createRoundRect(ctx, 250, 90, 55, 30, 15,  false);
498
            createRoundRect(ctx, 250, 130, 55, 30, 15, true);
499
            createRoundRect(ctx, 320, 90, 55, 30, 20,  false);
500
            createRoundRect(ctx, 320, 130, 55, 30, 20, true);
501
            createRoundRect(ctx, 390, 90, 55, 30, 25,  false);
502
            createRoundRect(ctx, 390, 130, 55, 30, 25, true);
503
            createRoundRect(ctx, 460, 90, 55, 30, 30,  false);
504
            createRoundRect(ctx, 460, 130, 55, 30, 30, true);
505
            
506
            // setColor(ctx, "#00FF00");
507
            setColor(ctx, "#99B4D1");
508
            create3DRect(ctx, 40, 175, 75, 30, false, true);
509
            create3DRect(ctx, 130, 175, 150, 60, false, true);
510
            create3DRect(ctx, 295, 175, 75, 30, true, true);
511
            create3DRect(ctx, 395, 175, 150, 60, true, true);
512
            create3DRect(ctx, 40, 250, 75, 30, false, false);
513
            create3DRect(ctx, 130, 250, 150, 60, false, false);
514
            create3DRect(ctx, 295, 250, 75, 30, true, false);
515
            create3DRect(ctx, 395, 250, 150, 60, true, false);
516
            setColor(ctx, "#000000");
517
            create3DRect(ctx, 40, 325, 75, 30, false, false);
518
            create3DRect(ctx, 130, 325, 150, 60, false, false);
519
            create3DRect(ctx, 295, 325, 75, 30, true, false);
520
            create3DRect(ctx, 395, 325, 150, 60, true, false);
521
            
522
            var xPoints = [ 40, 80, 40 ];
523
            var yPoints = [ 400, 420, 440 ];
524
            drawPolygon(ctx, xPoints, yPoints, 3, true);
525
            
526
            xPoints = [ 140, 100, 140 ];
527
            yPoints = [ 440, 420, 400 ];
528
            drawPolygon(ctx, xPoints, yPoints, 3, true);
529
            
530
            xPoints = [ 160, 180, 200 ];
531
            yPoints = [ 400, 440, 400 ];
532
            drawPolygon(ctx, xPoints, yPoints, 3, true);
533
            
534
            xPoints = [ 220, 240, 260 ];
535
            yPoints = [ 440, 400, 440 ];
536
            drawPolygon(ctx, xPoints, yPoints, 3, true);
537
            
538
            xPoints = [ 280, 300, 320, 310, 290 ];
539
            yPoints = [ 420, 440, 420, 400, 400 ];
540
            drawPolygon(ctx, xPoints, yPoints, 5, true);
541
            
542
            xPoints = [ 340, 360, 380, 370, 350 ];
543
            yPoints = [ 420, 400, 420, 440, 440 ];
544
            drawPolygon(ctx, xPoints, yPoints, 5, true);
545
         };
546
      </script>
547
   </head>
548
   <body>
549
      <canvas id="bogus" style="position:'absolute';">No HTML5 support in your browser!</canvas>
550
   </body>
551
</html>