├── .gitignore ├── README.md ├── bin └── node-canvas ├── binding.gyp ├── examples ├── arcTo.js ├── clipImage.js ├── clipping.js ├── clippingState.js ├── clock.js ├── color.js ├── doubleBuffering.js ├── drawImage.js ├── drawImageAlpha.js ├── ellipse.js ├── fonts.js ├── getImageData.js ├── globalAlpha.js ├── glyphs.js ├── gradients.js ├── imageSource.js ├── images │ ├── flurry.jpeg │ ├── grid.gif │ ├── grid.jpeg │ ├── grid.png │ ├── grid.pxm │ ├── grido2.png │ ├── grido4.png │ └── noisy_grid.png ├── lineDashOffset.js ├── lineDashState.js ├── pathObject.js ├── pathPrimitives.js ├── pathText.js ├── patterns.js ├── ray.js ├── screenshots │ ├── clipImage.png │ ├── clipping.png │ ├── clippingState.png │ ├── color.png │ ├── drawImage.png │ ├── drawImageAlpha.png │ ├── globalAlpha.png │ ├── imageSource_00.png │ ├── imageSource_01.png │ ├── imageSource_final.png │ ├── lineDashOffset.png │ ├── lineDashState.png │ ├── pathText.png │ └── shadows.png ├── shadows.js ├── shapes.js ├── spark.js ├── state.js ├── swissClock.js ├── textAlign.js ├── textDrawing.js ├── textSpeed.js └── util.js ├── lib ├── canvas.js ├── color.js ├── context.js ├── drawingStyle.js ├── gradient.js ├── image.js ├── matrix.js ├── path.js ├── pattern.js └── text │ ├── freetype.js │ ├── loading.js │ ├── rendering.js │ └── text.js ├── package.json ├── reference ├── arcTo.html ├── clipping.html ├── clippingState.html ├── ellipse.html ├── images │ ├── grido2.png │ ├── grido4.png │ └── noisy_grid.png ├── javascripts │ ├── arcTo.js │ └── requestAnimationFrame.js ├── lineDash.html ├── patterns.html ├── shadows.html └── stylesheets │ └── style.css ├── src ├── freeimage │ ├── freeimage.cc │ ├── freeimage.h │ ├── image.cc │ └── image.h ├── freetype.cc ├── freetype.h ├── util.cc ├── util.h └── v8_helpers.h └── tests ├── color.js └── image ├── convert-from-rgba-bits.js └── load-from-buffer.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | build 3 | 4 | .lock-wscript 5 | 6 | core 7 | .gdbinit 8 | .gdb_history 9 | v8.log 10 | 11 | ._* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-openvg-canvas 2 | 3 | [![NPM](https://nodei.co/npm/openvg-canvas.png?downloads=true&stars=true)](https://nodei.co/npm-dl/openvg-canvas/) [![NPM](https://nodei.co/npm-dl/openvg-canvas.png)](https://nodei.co/npm-dl/openvg-canvas/) 4 | 5 | ## Canvas implementation on node-openvg 6 | 7 | This module implements a HTML5 Canvas on top of OpenVG (node-openvg). It is targeted to the raspberry-pi. 8 | By using the OpenVG APIs on the Raspberry PI, all graphics code will run on the GPU with hardware acceleration. 9 | 10 | This library aims for API compatibility with [node-canvas](https://github.com/learnboost/node-canvas) where it applies and makes sense. While node-canvas is targeted to create images off screen, node-openvg-canvas is targeted to main screen usage, but not yet for user interaction. 11 | 12 | Currently there are only plans to implement the 2d context. Implementing the 3d context (web gl) should be possible by mapping OpenGL/ES. 13 | 14 | ## 0. Installation 15 | 16 | This module has been tested on node 0.8, 0.10 and 0.11. Official node binaries for the 17 | raspberry pi can be found be clicking at "Other release files" link that accompanies every 18 | node release announcement, [example](http://nodejs.org/dist/v0.11.4/). 19 | 20 | ### Prerequisites 21 | 22 | node-openvg-canvas depends on freetype and freeimage. To install these libraries 23 | on the raspberry - assuming a raspbian distribution - use: 24 | 25 | sudo apt-get install libfreetype6 libfreetype6-dev libfreeimage3 libfreeimage-dev 26 | 27 | ### Source code install 28 | 29 | Fetch the source: 30 | 31 | git clone https://github.com/luismreis/node-openvg-canvas.git 32 | 33 | Build the package: 34 | 35 | cd node-openvg-canvas 36 | npm install 37 | 38 | To test: 39 | 40 | export PATH=$PWD/bin:$PATH 41 | examples/swissClock.js 42 | 43 | ### NPM / module install 44 | 45 | Run on your command line: 46 | 47 | npm install openvg-canvas --save 48 | 49 | ## 1. Documentation 50 | 51 | ### Reference 52 | 53 | * [WHATWG](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html) 54 | * [w3c](http://www.w3.org/TR/2dcontext/) 55 | 56 | ### Canvas implementation status 57 | 58 | The project now implements the full HTML 5 Canvas Level 1 specification, plus most of 59 | the non interactive features of Level 2. The current focus on the project for releases in the short term is performance. 60 | 61 | Items marked as "✘" are not planned for implementation. Some due to insufficient information (eg. focus ring), others because they don't make sense in this implementation (eg. scrollPathIntoView). 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 |
Object / FeatureStatusNotes
CanvasRenderingContext2D
- state - save/restore
- matrix transformations: scale, transform, etc
- compositing - alpha, composite operation
- image smoothing
- stroke/fill style
- solid colors
- gradients
- patterns
(see below)
- shadows
- clear/fill/stroke rect
- beginPath, paths / path methods, fill, stroke
- focus ring
- scrollPathIntoView
- clipping region
- isPointInPathto doSupport planned.
- fill/stroke text
- measure texthanging and ideographic baselines not implemented.
- drawImage
- hit regions
- create/get/put image data
CanvasDrawingStyles
- line caps/joins - line width, cap, join, miter limit
- dashed lines
- text - font, textAlign, textBaseline
CanvasPathMethods
- beginPathAlso available on the Path object.
- moveTo, lineTo
- quadraticCurveTo, bezierCurveToUntested
- arcToImplemented Canvas Level 2 (elliptical arcs)
- rect
- arc
- ellipse
CanvasGradient
- addColorStop
CanvasPatternOpenVG doesn't support one-directional patterns. For now only 'no-repeat' and 'repeat' work as expected.
- setTransformto doPlanned.
TextMetrics
HitRegionOptions
ImageData
Path(see CanvasPathMethods)
- (constructor)SVG path constructor after v1.0
- addPath
- addPathByStrokingPath
- addTextPosition and Path variants
- addPathByStrokingText
116 | 117 | ### Differences from the HTML5 Canvas object / node-canvas 118 | 119 | On browsers, the Canvas rendering is controlled by the browser runtime - this is referred in the w3c docs as the ' "update the rendering" step'. 120 | 121 | On node-canvas, user code explicitly calls toBuffer or similar functions to produce output. Note that this behavior can be reproduced on node-openvg-canvas by calling ImageData.saveToBuffer and/or Canvas.toBuffer. 122 | 123 | Code running on node-openvg-canvas must explicitly swap display buffers, to do so, either call ```Canvas.vgSwapBuffers()``` or use the included requestAnimationFrame shim that does this after calling your paint callback function (for more information look at the clock examples). 124 | 125 | ## Some sample screenshots 126 | The code to produce these screenshots is included in the examples. 127 | 128 | ### Colors parsing 129 | examples/color.js 130 | 131 | ![Color](https://raw.github.com/luismreis/node-openvg-canvas/master/examples/screenshots/color.png) 132 | 133 | ### Clipping 134 | examples/clipping.js 135 | 136 | ![Clipping](https://raw.github.com/luismreis/node-openvg-canvas/master/examples/screenshots/clipping.png) 137 | 138 | ### Alpha 139 | examples/globalAlpha.js 140 | 141 | ![globalAlpha](https://raw.github.com/luismreis/node-openvg-canvas/master/examples/screenshots/globalAlpha.png) 142 | 143 | ### Shadows 144 | examples/clipping.js 145 | 146 | ![Shadows](https://raw.github.com/luismreis/node-openvg-canvas/master/examples/screenshots/shadows.png) 147 | 148 | ### Text along paths 149 | examples/pathText.js 150 | 151 | ![Path Text](https://raw.github.com/luismreis/node-openvg-canvas/master/examples/screenshots/pathText.png) 152 | 153 | ## License 154 | 155 | (The MIT License) 156 | 157 | Copyright (c) 2012, 2013 Luis Reis 158 | 159 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 160 | 161 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 162 | 163 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 164 | 165 | ==== 166 | 167 | This module used to depend on node-image (BSD license) [npm](https://npmjs.org/package/node-image) / [github](https://github.com/mikeseven/node-image) for image loading/saving, currently, it has it's own [FreeImage](http://freeimage.sourceforge.net/license.html) bindings. 168 | 169 | ==== 170 | 171 | Font loading and rendering via [FreeType](http://www.freetype.org/) is based on code originaly from [Hybrid Graphics, Ltd](http://web.archive.org/web/20070808195023/http://developer.hybrid.fi/font2openvg/index.html) (BSD license). 172 | -------------------------------------------------------------------------------- /bin/node-canvas: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | FULL=$PWD/$0 4 | CANONIC=`readlink -f $0` 5 | 6 | if [ "$CANONIC" = "$FULL" ]; then 7 | BIN_DIR=`dirname $CANONIC` 8 | MODULE_DIR=`dirname $BIN_DIR` 9 | else 10 | NPM_DIR=`dirname $CANONIC` 11 | MODULE_DIR=`dirname $NPM_DIR` 12 | fi 13 | 14 | $MODULE_DIR/node_modules/openvg/bin/node-openvg "$@" 15 | -------------------------------------------------------------------------------- /binding.gyp: -------------------------------------------------------------------------------- 1 | { 2 | "variables": { 3 | "platform": "<(OS)", 4 | "buffer_impl" : " 0 || v[0] == 0 && v[1] >= 11 ? \"POS_0_11\" : \"PRE_0_11\"')", 5 | "callback_style" : " 3 || v[0] == 3 && v[1] >= 20 ? \"POS_3_20\" : \"PRE_3_20\"')" 6 | }, 7 | "conditions": [ 8 | # Replace gyp platform with node platform, blech 9 | ["platform == \"mac\"", {"variables": {"platform": "darwin"}}], 10 | ["platform == \"win\"", {"variables": {"platform": "win32"}}], 11 | ], 12 | "targets": [ 13 | { 14 | "target_name": "freetype", 15 | "sources": [ 16 | "src/freetype.cc", 17 | "src/util.cc" 18 | ], 19 | "defines": [ 20 | "NODE_BUFFER_TYPE_<(buffer_impl)", 21 | "TYPED_ARRAY_TYPE_<(buffer_impl)", 22 | "SCOPE_DECL_<(buffer_impl)", 23 | "V8_CALLBACK_STYLE_<(callback_style)" 24 | ], 25 | "ldflags": [ 26 | " 0 ? 1 : -1; 55 | var cos_alpha = dot_prod / (mod_v1 * mod_v2); 56 | var sin_alpha = Math.sqrt(1 - cos_alpha * cos_alpha); 57 | 58 | // tan(x) = sin(2x) / (cos(2x) + 1) 59 | // tan(x/2) = sin(x) / (cos(x) + 1) 60 | // r = t * tan(alpha/2) 61 | // t = rY * sin_alpha / (1 + cos_alpha); 62 | var t = radiusY * sin_alpha / (1 + cos_alpha); 63 | var pstart_ = { x: p2_.x - t * v1.x / mod_v1, y: p2_.y - t * v1.y / mod_v1 }; 64 | var pstart = reverseTransform(pstart_.x, pstart_.y); 65 | 66 | var pend_ = { x: p2_.x + t * v2.x / mod_v2, y: p2_.y + t * v2.y / mod_v2 }; 67 | var pend = reverseTransform(pend_.x, pend_.y); 68 | 69 | // To use OpenVG [s/l][c/cc]arc_to erything needed is already here 70 | // s / l = always short 71 | // cc = sign < 0 72 | 73 | var center_ = { x: pstart_.x - sign * radiusY * v1.y / mod_v1, 74 | y: pstart_.y + sign * radiusY * v1.x / mod_v1 }; 75 | 76 | var center = reverseTransform(center_.x, center_.y); 77 | 78 | v1 = reverseTransform(v1.x, v1.y); 79 | v2 = reverseTransform(v2.x, v2.y); 80 | mod_v1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y); 81 | mod_v2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y); 82 | 83 | // baselines 84 | ctx.lineWidth = 2; 85 | ctx.strokeStyle = 'red'; 86 | 87 | ctx.beginPath(); 88 | ctx.moveTo(p1.x - 20 * v1.x / mod_v1, p1.y - 20 * v1.y / mod_v1); 89 | ctx.lineTo(p2.x + 20 * v1.x / mod_v1, p2.y + 20 * v1.y / mod_v1); 90 | ctx.moveTo(p2.x - 20 * v2.x / mod_v2, p2.y - 20 * v2.y / mod_v2); 91 | ctx.lineTo(p3.x + 20 * v2.x / mod_v2, p3.y + 20 * v2.y / mod_v2); 92 | ctx.stroke(); 93 | 94 | var rc = 4; 95 | 96 | if (cross_prod !== 0) { 97 | // radius 98 | ctx.strokeStyle = '#00f'; 99 | ctx.beginPath(); 100 | if (radiusX === radiusY) { 101 | ctx.moveTo(center.x - radiusX * Math.sqrt(2) / 2, 102 | center.y + radiusY * Math.sqrt(2) / 2); 103 | ctx.lineTo(center.x, center.y); 104 | } else { 105 | ctx.moveTo(center.x + radiusX * Math.cos(Math.PI + rotation), 106 | center.y + radiusX * Math.sin(Math.PI + rotation)); 107 | ctx.lineTo(center.x, center.y); 108 | ctx.lineTo(center.x + radiusY * Math.cos(Math.PI / 2 + rotation), 109 | center.y + radiusY * Math.sin(Math.PI / 2 + rotation)); 110 | } 111 | ctx.stroke(); 112 | 113 | // start, end angles 114 | ctx.strokeStyle = '#0080ff'; 115 | var start_angle = Math.atan2(pstart_.y - center_.y, pstart_.x - center_.x); 116 | var end_angle = Math.atan2(pend_.y - center_.y, pend_.x - center_.x); 117 | ctx.beginPath(); 118 | ctx.moveTo(pstart.x, pstart.y); 119 | ctx.lineTo(center.x, center.y); 120 | ctx.lineTo(pend.x, pend.y); 121 | ctx.stroke(); 122 | 123 | // base circle 124 | ctx.strokeStyle = '#888'; 125 | ctx.beginPath(); 126 | ctx.moveTo(center.x + radiusX, center.y); 127 | ctx.ellipse(center.x, center.y, radiusX, radiusY, rotation, 0, 2 * Math.PI, false); 128 | ctx.stroke(); 129 | 130 | ctx.globalAlpha = 0.5; 131 | ctx.lineCap = 'round'; 132 | ctx.strokeStyle = '#00de28'; 133 | ctx.lineWidth = 2 * (rc + 2); 134 | 135 | ctx.beginPath(); 136 | 137 | ctx.moveTo(p1.x, p1.y); 138 | if (softArc) { 139 | // The following code corresponds to: 140 | // ctx.arcTo(p2.x, p2.y, p3.x, p3.y, r); 141 | 142 | // leading line (x0, y0 -> startx, starty) 143 | ctx.lineTo(pstart.x, pstart.y); 144 | 145 | // the arc 146 | ctx.ellipse(center.x, center.y, 147 | radiusX, radiusY, rotation, start_angle, end_angle, 148 | sign < 0); 149 | } else { 150 | ctx.arcTo(p2.x, p2.y, p3.x, p3.y, radiusX, radiusY, rotation); 151 | } 152 | 153 | ctx.stroke(); 154 | 155 | ctx.globalAlpha = 1.0; 156 | ctx.lineCap = 'butt'; 157 | } 158 | 159 | function dot(p) { 160 | ctx.moveTo(p.x + rc, p.y); 161 | ctx.arc(p.x, p.y, rc, 0, 2 * Math.PI, false); 162 | } 163 | 164 | ctx.fillStyle = 'white'; 165 | ctx.lineWidth = 1; 166 | 167 | // control points 168 | ctx.beginPath(); 169 | dot(p1); 170 | dot(p2); 171 | dot(p3); 172 | if (cross_prod !== 0) { dot(center); } 173 | ctx.fill(); 174 | 175 | if (cross_prod !== 0) { 176 | ctx.fillStyle = '#0f0'; 177 | ctx.beginPath(); 178 | dot(pstart); 179 | dot(pend); 180 | ctx.fill(); 181 | } 182 | } 183 | 184 | function paint(now) { 185 | ctx.clearRect(0, 0, canvas.width, canvas.height); 186 | // ctx.save(); 187 | 188 | var demo = Math.floor(now / 6000) % 3; 189 | if (demo === 0) { 190 | radiusX = 20; 191 | radiusY = 20; 192 | rotation = 0; 193 | } else if (demo === 1) { 194 | radiusX = 40; 195 | radiusY = 10; 196 | rotation = 0; 197 | } else { 198 | radiusX = 40; 199 | radiusY = 10; 200 | rotation = Math.PI / 6; 201 | } 202 | 203 | var p = [{ x: 400 - 40, y: 100}, { x: 400, y: 100}, { x: 400, y: 140}]; 204 | var deltaT = now % 6000; 205 | if (deltaT < 1000) { 206 | p[0].x += deltaT / 40; 207 | } else if (deltaT < 2000) { 208 | p[0].x += 25 - (deltaT - 1000) / 40; 209 | } else if (deltaT < 3000) { 210 | p[1].x += (deltaT - 2000) / 50; 211 | p[1].y -= (deltaT - 2000) / 50; 212 | } else if (deltaT < 4000) { 213 | p[1].x += 20 - (deltaT - 3000) / 50; 214 | p[1].y -= 20 - (deltaT - 3000) / 50; 215 | } else if (deltaT < 5000) { 216 | p[2].x -= (deltaT - 4000) / 50; 217 | p[2].y -= (deltaT - 4000) / 50; 218 | } else { 219 | p[2].x -= 20 - (deltaT - 5000) / 50; 220 | p[2].y -= 20 - (deltaT - 5000) / 50; 221 | } 222 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 223 | 224 | var rr = 40; 225 | var center; 226 | function controlPoint(angle) { 227 | return { x: center.x + rr * Math.cos(Math.PI * angle), 228 | y: center.y + rr * Math.sin(Math.PI * angle) }; 229 | } 230 | 231 | center = { x: 100, y: 100 }; 232 | p = [controlPoint(5 / 4), center, controlPoint(deltaT / 3000)]; 233 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 234 | 235 | center = { x: 200, y: 100 }; 236 | p = [controlPoint(1), center, controlPoint(deltaT / 3000)]; 237 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 238 | 239 | center = { x: 300, y: 100 }; 240 | p = [controlPoint(3 / 2), center, controlPoint(deltaT / 3000)]; 241 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 242 | 243 | 244 | center = { x: 100, y: 200 }; 245 | p = [controlPoint(deltaT / 3000), center, controlPoint(5 / 4)]; 246 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 247 | 248 | center = { x: 200, y: 200 }; 249 | p = [controlPoint(deltaT / 3000), center, controlPoint(1)]; 250 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 251 | 252 | center = { x: 300, y: 200 }; 253 | p = [controlPoint(deltaT / 3000), center, controlPoint(3 / 2)]; 254 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 255 | 256 | center = { x: 100, y: 300 }; 257 | p = [controlPoint(5 / 4), controlPoint(deltaT / 3000), controlPoint(0)]; 258 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 259 | 260 | center = { x: 200, y: 300 }; 261 | p = [controlPoint(1), controlPoint(deltaT / 3000), controlPoint(0)]; 262 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 263 | 264 | center = { x: 300, y: 300 }; 265 | p = [controlPoint(3 / 2), controlPoint(deltaT / 3000), controlPoint(0)]; 266 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 267 | 268 | // ctx.restore(); 269 | } 270 | 271 | eu.animate(paint); 272 | 273 | eu.handleTermination(); 274 | 275 | eu.waitForInput(); 276 | -------------------------------------------------------------------------------- /examples/clipImage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global Image: true */ 4 | "use strict"; 5 | 6 | var fs = require('fs'); 7 | var util = require('util'); 8 | var Canvas = require('../lib/canvas'); 9 | var Image = Canvas.Image; 10 | var canvas = new Canvas(); 11 | var ctx = canvas.getContext('2d'); 12 | var eu = require('./util'); 13 | 14 | var width = 64, height = 64; 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | var img = new Image(); 18 | img.src = fs.readFileSync(__dirname + '/images/grido2.png'); 19 | 20 | var forceSaveRestore = false; 21 | 22 | function defineMask(callback) { 23 | var radius = 24; 24 | 25 | ctx.translate(width / 2, height / 2); 26 | ctx.arc(0 /* x */, 0 /* y */, radius /* radius */, 0, 2 * Math.PI, false); 27 | 28 | if (callback) callback(); 29 | 30 | ctx.translate(-width / 2, -height / 2); 31 | } 32 | 33 | var clip = false; 34 | function setMask() { 35 | if (clip) { return; } 36 | 37 | if (!ctx.resetClip || forceSaveRestore) { 38 | // Without resetClip there's no way to reset the clipping region 39 | // except for save/restore. 40 | ctx.save(); 41 | } 42 | // defineMask(function () { 43 | // ctx.strokeStyle = 'white'; 44 | // ctx.stroke(); 45 | // }); 46 | defineMask(function () { 47 | ctx.clip(); 48 | }); 49 | defineMask(); 50 | clip = true; 51 | } 52 | 53 | function resetMask() { 54 | if (!clip) { return; } 55 | if (ctx.resetClip && !forceSaveRestore) { 56 | ctx.resetClip(); 57 | } else { 58 | // Without resetClip there's no way to reset the clipping region 59 | // except for save/restore. 60 | ctx.restore(); 61 | } 62 | clip = false; 63 | } 64 | 65 | function paint() { 66 | process.argv.forEach(function (val, index, array) { 67 | if ('-f' === val) { 68 | forceSaveRestore = true; 69 | } 70 | }); 71 | 72 | setMask(); 73 | ctx.drawImage(img, 0, 0); 74 | resetMask(); 75 | } 76 | 77 | paint(); 78 | canvas.vgSwapBuffers(); 79 | 80 | eu.saveScreenshot(ctx, 0, 0, width, height, 81 | 'examples/screenshots/clipImage.png'); 82 | console.log('Screenshot taken.'); 83 | 84 | eu.handleTermination(); 85 | eu.waitForInput(); 86 | -------------------------------------------------------------------------------- /examples/clipping.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(); 8 | var ctx = canvas.getContext('2d'); 9 | var eu = require('./util'); 10 | 11 | var width = 500, height = 400; 12 | ctx.clearRect(0, 0, canvas.width, canvas.height); 13 | 14 | var forceSaveRestore = false; 15 | 16 | function defineMask(callback) { 17 | var radius = height / 10; 18 | var angle = Math.PI / 3; 19 | 20 | ctx.translate(width / 2, height / 3); 21 | ctx.beginPath(); 22 | ctx.moveTo(radius * Math.cos(angle), radius * Math.sin(angle)); 23 | ctx.arc(0 /* x */, 0 /* y */, radius /* radius */, angle, Math.PI - angle, true); 24 | ctx.lineTo(-radius * 3 / 4, radius * 3); 25 | ctx.lineTo(radius * 3 / 4, radius * 3); 26 | ctx.closePath(); 27 | 28 | if (callback) callback(); 29 | 30 | ctx.translate(-width / 2, -height / 3); 31 | } 32 | 33 | var clip = false; 34 | function setMask() { 35 | if (clip) { return; } 36 | 37 | if (!ctx.resetClip || forceSaveRestore) { 38 | // Without resetClip there's no way to reset the clipping region 39 | // except for save/restore. 40 | ctx.save(); 41 | } 42 | // defineMask(function () { 43 | // ctx.strokeStyle = 'white'; 44 | // ctx.stroke(); 45 | // }); 46 | defineMask(function () { 47 | ctx.clip(); 48 | }); 49 | defineMask(); 50 | clip = true; 51 | } 52 | 53 | function resetMask() { 54 | if (!clip) { return; } 55 | if (ctx.resetClip && !forceSaveRestore) { 56 | ctx.resetClip(); 57 | } else { 58 | // Without resetClip there's no way to reset the clipping region 59 | // except for save/restore. 60 | ctx.restore(); 61 | } 62 | clip = false; 63 | } 64 | 65 | function setup() { 66 | ctx.fillStyle = 'black'; 67 | ctx.fillRect(0, 0, width, height); 68 | 69 | process.argv.forEach(function (val, index, array) { 70 | if ('-f' === val) { 71 | forceSaveRestore = true; 72 | } 73 | }); 74 | 75 | ctx.lineWidth = 2; 76 | setMask(); 77 | ctx.strokeStyle = 'rgba(128,0,0,0.5)'; 78 | } 79 | 80 | var startTime = undefined; 81 | function paint(time) { 82 | var color = Math.floor(Math.random() * 255); 83 | var alpha = Math.random(); 84 | if (startTime) { 85 | if (((time - startTime) / 1000) % 4 < 2) { 86 | setMask(); 87 | ctx.strokeStyle = 'rgba(' + color + ',0,0,' + (0.5 + alpha * 0.5) + ')'; 88 | } else { 89 | resetMask(); 90 | ctx.strokeStyle = 'rgba(0,0,' + color + ',' + alpha * 0.5 + ')'; 91 | } 92 | } else { 93 | startTime = time; 94 | } 95 | 96 | ctx.beginPath(); 97 | ctx.moveTo(Math.random() * width, Math.random() * height); 98 | ctx.lineTo(Math.random() * width, Math.random() * height); 99 | ctx.stroke(); 100 | } 101 | 102 | setup(); 103 | 104 | eu.animate(paint); 105 | 106 | eu.handleTermination(); 107 | 108 | setTimeout(function () { 109 | eu.saveScreenshot(ctx, 0, 0, width, height, 110 | 'examples/screenshots/clipping.png'); 111 | console.log('Screenshot taken.'); 112 | }, 2 * 60 * 1000); 113 | 114 | eu.waitForInput(); 115 | -------------------------------------------------------------------------------- /examples/clippingState.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(); 8 | var ctx = canvas.getContext('2d'); 9 | var eu = require('./util'); 10 | 11 | var width = 500, height = 400; 12 | ctx.clearRect(0, 0, canvas.width, canvas.height); 13 | 14 | function paint(time) { 15 | ctx.fillStyle = 'black'; 16 | ctx.fillRect(0, 0, width, height); 17 | 18 | ctx.save(); 19 | 20 | ctx.beginPath(); 21 | ctx.rect(50, 50, 100, 100); 22 | ctx.clip(); 23 | 24 | ctx.fillStyle = 'rgba(255,0,0,0.5)'; 25 | ctx.fillRect(0, 0, width, height); 26 | 27 | ctx.save(); 28 | 29 | ctx.beginPath(); 30 | ctx.rect(100, 100, 100, 100); 31 | ctx.clip(); 32 | 33 | ctx.fillStyle = 'rgba(0,255,0,0.5)'; 34 | ctx.fillRect(0, 0, width, height); 35 | 36 | ctx.restore(); 37 | 38 | ctx.beginPath(); 39 | ctx.rect(75, 125, 100, 100); 40 | ctx.clip(); 41 | 42 | ctx.fillStyle = 'rgba(0,0,255,0.5)'; 43 | ctx.fillRect(0, 0, width, height); 44 | 45 | ctx.restore(); 46 | 47 | ctx.beginPath(); 48 | ctx.rect(125, 75, 100, 100); 49 | ctx.clip(); 50 | 51 | ctx.fillStyle = 'rgba(255,255,0,0.5)'; 52 | ctx.fillRect(0, 0, width, height); 53 | } 54 | 55 | paint(); 56 | 57 | canvas.vgSwapBuffers(); 58 | 59 | eu.saveScreenshot(ctx, 0, 0, width, height, 60 | 'examples/screenshots/clippingState.png'); 61 | console.log('Screenshot taken.'); 62 | 63 | eu.handleTermination(); 64 | eu.waitForInput(); 65 | -------------------------------------------------------------------------------- /examples/clock.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/clock.js 7 | 8 | var fs = require('fs'); 9 | var util = require('util'); 10 | 11 | var vg = require('openvg'); 12 | var eu = require('./util'); 13 | 14 | var Canvas = require('../lib/canvas'); 15 | var canvas = new Canvas(320, 320); 16 | var ctx = canvas.getContext('2d'); 17 | 18 | function getX(angle) { 19 | return -Math.sin(angle + Math.PI); 20 | } 21 | function getY(angle) { 22 | return Math.cos(angle + Math.PI); 23 | } 24 | 25 | function clock(ctx) { 26 | var now = new Date(); 27 | var x, y, i; 28 | //ctx.clearRect(0,0,320,320); 29 | ctx.clearRect(0, 0, canvas.width, canvas.height); 30 | 31 | ctx.save(); 32 | ctx.scale(3, 3); 33 | ctx.translate(canvas.width / 6, canvas.height / 6); 34 | 35 | ctx.beginPath(); 36 | ctx.lineWidth = 14; 37 | ctx.strokeStyle = '#325FA2'; 38 | ctx.fillStyle = '#eeeeee'; 39 | ctx.arc(0, 0, 142, 0, Math.PI * 2, false); 40 | ctx.stroke(); 41 | ctx.fill(); 42 | 43 | ctx.strokeStyle = '#000000'; 44 | ctx.beginPath(); 45 | // Hour marks 46 | ctx.lineWidth = 8; 47 | for (i = 0; i < 12; i++) { 48 | x = getX(Math.PI / 6 * i); 49 | y = getY(Math.PI / 6 * i); 50 | ctx.moveTo(x * 100, y * 100); 51 | ctx.lineTo(x * 125, y * 125); 52 | } 53 | ctx.stroke(); 54 | 55 | // Minute marks 56 | ctx.lineWidth = 5; 57 | ctx.beginPath(); 58 | for (i = 0; i < 60; i++) { 59 | if (i % 5 !== 0) { 60 | x = getX(Math.PI / 30 * i); 61 | y = getY(Math.PI / 30 * i); 62 | ctx.moveTo(x * 117, y * 117); 63 | ctx.lineTo(x * 125, y * 125); 64 | } 65 | } 66 | ctx.stroke(); 67 | 68 | var sec = now.getSeconds(); 69 | var min = now.getMinutes(); 70 | var hr = now.getHours(); 71 | hr = hr >= 12 ? hr - 12 : hr; 72 | 73 | ctx.fillStyle = "black"; 74 | 75 | // write Hours 76 | x = getX(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec); 77 | y = getY(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec); 78 | ctx.lineWidth = 14; 79 | ctx.beginPath(); 80 | ctx.moveTo(x * -20, y * -20); 81 | ctx.lineTo(x * 80, y * 80); 82 | ctx.stroke(); 83 | 84 | // write Minutes 85 | x = getX((Math.PI / 30) * min + (Math.PI / 1800) * sec); 86 | y = getY((Math.PI / 30) * min + (Math.PI / 1800) * sec); 87 | 88 | ctx.lineWidth = 10; 89 | ctx.beginPath(); 90 | ctx.moveTo(x * -28, y * -28); 91 | ctx.lineTo(x * 112, y * 112); 92 | ctx.stroke(); 93 | 94 | // Write seconds 95 | x = getX(sec * Math.PI / 30); 96 | y = getY(sec * Math.PI / 30); 97 | ctx.strokeStyle = "#D40000"; 98 | ctx.fillStyle = "#D40000"; 99 | ctx.lineWidth = 6; 100 | ctx.beginPath(); 101 | ctx.moveTo(x * -30, y * -30); 102 | ctx.lineTo(x * 83, y * 83); 103 | ctx.stroke(); 104 | ctx.beginPath(); 105 | ctx.arc(0, 0, 10, 0, Math.PI * 2, false); 106 | ctx.fill(); 107 | ctx.beginPath(); 108 | ctx.arc(x * 95, y * 95, 10, 0, Math.PI * 2, false); 109 | ctx.stroke(); 110 | ctx.fillStyle = "#555"; 111 | ctx.arc(0, 0, 3, 0, Math.PI * 2, false); 112 | ctx.fill(); 113 | ctx.restore(); 114 | } 115 | 116 | eu.animate(function (time) { 117 | clock(ctx); 118 | }); 119 | 120 | eu.handleTermination(); 121 | 122 | eu.waitForInput(); 123 | -------------------------------------------------------------------------------- /examples/color.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var Canvas = require('../lib/canvas'); 6 | var canvas = new Canvas(320, 320); 7 | var ctx = canvas.getContext('2d'); 8 | var eu = require('./util'); 9 | 10 | var h_stops = 12; 11 | var s_stops = 32; 12 | var l_stops = 32; 13 | 14 | var ww = 2; 15 | var hh = 2; 16 | var margin = 0; 17 | 18 | var width = 4 * ww * s_stops + 2 * 10, 19 | height = 3 * hh * l_stops + 2 * 10; 20 | 21 | ctx.clearRect(0, 0, canvas.width, canvas.height); 22 | ctx.fillStyle = '#404040'; 23 | ctx.fillRect(0, 0, width, height); 24 | 25 | // ctx.fillStyle = 'red'; 26 | 27 | for (var i = 0; i < h_stops; i++) { 28 | for (var j = 0; j < l_stops; j++) { 29 | for (var k = 0; k < s_stops; k++) { 30 | var h = i * 360 / h_stops; 31 | var s = 100 - 100 * k / (s_stops - 1); 32 | var l = 100 - 100 * j / (l_stops - 1); 33 | ctx.fillStyle = 'hsl(' + h + ', ' + s + '%, ' + l + '%)'; 34 | ctx.fillRect(10 + (i % 4) * (s_stops * ww) + k * (ww + margin), 35 | 10 + Math.floor(i / 4) * (l_stops * hh) + j * (hh + margin), 36 | ww, hh); 37 | } 38 | } 39 | } 40 | 41 | canvas.vgSwapBuffers(); 42 | 43 | eu.saveScreenshot(ctx, 0, 0, width, height, 44 | 'examples/screenshots/color.png'); 45 | console.log('Screenshot taken.'); 46 | 47 | eu.handleTermination(); 48 | eu.waitForInput(); 49 | -------------------------------------------------------------------------------- /examples/doubleBuffering.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var Canvas = require('../lib/canvas'); 6 | var canvas = new Canvas(200, 200); 7 | var ctx = canvas.getContext('2d'); 8 | 9 | var eu = require('./util'); 10 | 11 | ctx.font = canvas.height / 4 + 'px sans'; 12 | ctx.textAlign = 'center'; 13 | ctx.textBaseline = 'middle'; 14 | ctx.fillStyle = 'red'; 15 | 16 | var i = 0; 17 | (function drawText() { 18 | if (i === 0) ctx.clearRect(0, 0, canvas.width, canvas.height); 19 | ctx.fillText(i.toString(), 20 | canvas.width * (i % 5) / 5 + canvas.width / 10, 21 | canvas.height * Math.floor(i / 5) / 2 + canvas.height / 4); 22 | canvas.vgSwapBuffers(); 23 | 24 | i++; 25 | if (i === 10) i = 0; 26 | 27 | setTimeout(drawText, 1000); 28 | })(); 29 | eu.handleTermination(); 30 | 31 | eu.waitForInput(function () { 32 | process.exit(0); 33 | }); 34 | -------------------------------------------------------------------------------- /examples/drawImage.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global Image: true */ 4 | "use strict"; 5 | 6 | var Canvas = require('../lib/canvas'); 7 | var Image = Canvas.Image; 8 | var canvas = new Canvas(200, 200); 9 | var ctx = canvas.getContext('2d'); 10 | var fs = require('fs'); 11 | 12 | var eu = require('./util'); 13 | 14 | var grid = fs.readFileSync(__dirname + '/images/grid.gif'); 15 | var img = new Image(); 16 | img.src = grid; 17 | 18 | var x0 = 64, y0 = 64; 19 | var x = x0, y = y0; 20 | 21 | ctx.clearRect(0, 0, canvas.width, canvas.height); 22 | ctx.imageSmoothingEnabled = false; 23 | 24 | ctx.fillStyle = '#fff'; 25 | ctx.fillRect(x, y, 128, 128); // This rect shouldn't be visible 26 | ctx.drawImage(img, 64, 64); // Default to image width, height 27 | 28 | x += 128; 29 | x += 64; 30 | ctx.fillRect(x, y, 256, 256); // This rect shouldn't be visible 31 | ctx.drawImage(img, x, y, 256, 256); 32 | 33 | x += 256; 34 | x += 64; 35 | ctx.fillRect(x, y, 64, 64); // This rect shouldn't be visible 36 | ctx.drawImage(img, x, y, 64, 64); 37 | 38 | x = x0; 39 | y += 256; 40 | y += 64; 41 | 42 | ctx.fillRect(x, y, 128, 128); // This rect shouldn't be visible 43 | ctx.drawImage(img, 0, 0, 128, 128, x, y, 128, 128); 44 | 45 | x += 128; 46 | x += 64; 47 | ctx.fillRect(x, y, 128, 128); // This rect shouldn't be visible 48 | ctx.drawImage(img, 64, 64, 64, 64, x, y, 128, 128); 49 | 50 | x += 128; 51 | x += 64; 52 | ctx.fillRect(x, y, 256, 256); // This rect shouldn't be visible 53 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 256, 256); 54 | 55 | x += 256; 56 | x += 64; 57 | ctx.fillRect(x, y, 32, 32); // This rect shouldn't be visible 58 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 32, 32); 59 | 60 | x = x0; 61 | y += 256; 62 | y += 64; 63 | 64 | ctx.fillRect(x, y, 128, 64); // This rect shouldn't be visible 65 | ctx.drawImage(img, 0, 0, 128, 128, x, y, 128, 64); 66 | 67 | x += 128; 68 | x += 64; 69 | ctx.fillRect(x, y, 64, 64); // This rect shouldn't be visible 70 | ctx.drawImage(img, 64, 64, 128, 64, x, y, 128, 64); 71 | 72 | x += 128; 73 | x += 64; 74 | ctx.fillRect(x, y, 256, 128); // This rect shouldn't be visible 75 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 256, 128); 76 | 77 | x += 256; 78 | x += 64; 79 | ctx.fillRect(x, y, 64, 128); // This rect shouldn't be visible 80 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 64, 128); 81 | 82 | canvas.vgSwapBuffers(); 83 | 84 | eu.saveScreenshot(ctx, 0, 0, 1024, 768, 85 | 'examples/screenshots/drawImage.png'); 86 | 87 | eu.waitForInput(); 88 | -------------------------------------------------------------------------------- /examples/drawImageAlpha.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global Image: true */ 4 | "use strict"; 5 | 6 | var Canvas = require('../lib/canvas'); 7 | var Image = Canvas.Image; 8 | var canvas = new Canvas(200, 200); 9 | var ctx = canvas.getContext('2d'); 10 | var fs = require('fs'); 11 | 12 | var eu = require('./util'); 13 | 14 | var grid = fs.readFileSync(__dirname + '/images/grid.gif'); 15 | var img = new Image(); 16 | img.src = grid; 17 | 18 | var x0 = 64, y0 = 64; 19 | var x = x0, y = y0; 20 | 21 | ctx.clearRect(0, 0, canvas.width, canvas.height); 22 | ctx.imageSmoothingEnabled = false; 23 | 24 | ctx.fillStyle = '#fff'; 25 | ctx.fillRect(x, y, 128, 128); // This rect shouldn't be visible 26 | ctx.drawImage(img, 64, 64); // Default to image width, height 27 | 28 | x += 128; 29 | x += 64; 30 | ctx.fillRect(x, y, 256, 256); // This rect shouldn't be visible 31 | ctx.drawImage(img, x, y, 256, 256); 32 | 33 | x += 256; 34 | x += 64; 35 | ctx.fillRect(x, y, 64, 64); // This rect shouldn't be visible 36 | ctx.drawImage(img, x, y, 64, 64); 37 | 38 | x = x0; 39 | y += 256; 40 | y += 64; 41 | 42 | ctx.fillRect(x, y, 128, 128); // This rect should be partially visible 43 | ctx.globalAlpha = 0.5; 44 | ctx.drawImage(img, 0, 0, 128, 128, x, y, 128, 128); 45 | ctx.globalAlpha = 1; 46 | 47 | x += 128; 48 | x += 64; 49 | ctx.fillRect(x, y, 128, 128); // This rect shouldn't be visible 50 | ctx.drawImage(img, 64, 64, 64, 64, x, y, 128, 128); 51 | 52 | x += 128; 53 | x += 64; 54 | ctx.globalAlpha = 0.7; 55 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 256, 256); // This should be partially visible 56 | ctx.globalAlpha = 1; 57 | 58 | x += 256; 59 | x += 64; 60 | ctx.fillRect(x, y, 32, 32); // This rect shouldn't be visible 61 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 32, 32); 62 | 63 | x = x0; 64 | y += 256; 65 | y += 64; 66 | 67 | ctx.fillRect(x, y, 128, 64); // This rect shouldn't be visible 68 | ctx.drawImage(img, 0, 0, 128, 128, x, y, 128, 64); 69 | 70 | x += 128; 71 | x += 64; 72 | ctx.fillRect(x, y, 64, 64); // This rect shouldn't be visible 73 | ctx.drawImage(img, 64, 64, 128, 64, x, y, 128, 64); 74 | 75 | x += 128; 76 | x += 64; 77 | ctx.fillRect(x, y, 256, 128); // This rect shouldn't be visible 78 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 256, 128); 79 | 80 | x += 256; 81 | x += 64; 82 | ctx.fillRect(x, y, 64, 128); // This rect shouldn't be visible 83 | ctx.drawImage(img, 32, 32, 64, 64, x, y, 64, 128); 84 | 85 | Canvas.vgSwapBuffers(); 86 | 87 | eu.saveScreenshot(ctx, 0, 0, 1024, 768, 88 | 'examples/screenshots/drawImageAlpha.png'); 89 | 90 | eu.waitForInput(); 91 | -------------------------------------------------------------------------------- /examples/ellipse.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var Canvas = require('../lib/canvas'); 6 | var canvas = new Canvas(320, 320); 7 | var ctx = canvas.getContext('2d'); 8 | var eu = require('./util'); 9 | 10 | ctx.clearRect(0, 0, canvas.width, canvas.height); 11 | 12 | ctx.beginPath(); 13 | ctx.lineWidth = 5; 14 | 15 | var segments = 200; 16 | var x = 100; 17 | var y = 100; 18 | var radiusX = 100; 19 | var radiusY = 50; 20 | var startAngle = 0; 21 | var endAngle = Math.PI / 2; 22 | var rotation = Math.PI / 6; 23 | 24 | function rotate(p, angle) { 25 | var x = p.x * Math.cos(angle) - p.y * Math.sin(angle); 26 | p.y = p.x * Math.sin(angle) + p.y * Math.cos(angle); 27 | p.x = x; 28 | } 29 | 30 | function ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle) { 31 | var p = { x: radiusX * Math.cos(startAngle), y: radiusY * Math.sin(startAngle) }; 32 | rotate(p, rotation); 33 | ctx.moveTo(x + p.x, y + p.y); 34 | 35 | for (var i = 1; i <= segments; i++) { 36 | p.x = radiusX * Math.cos(startAngle + i * (endAngle - startAngle) / segments); 37 | p.y = radiusY * Math.sin(startAngle + i * (endAngle - startAngle) / segments); 38 | rotate(p, rotation); 39 | ctx.lineTo(x + p.x, y + p.y); 40 | // console.log('x: ' + (x + px) + '\n' + 41 | // 'y: ' + (y + py)); 42 | } 43 | } 44 | 45 | ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle); 46 | 47 | ctx.strokeStyle = 'white'; 48 | ctx.beginPath(); 49 | ctx.ellipse(x, y, radiusX, radiusY, rotation, 0, 2 * Math.PI, false); 50 | ctx.stroke(); 51 | ctx.beginPath(); 52 | ctx.ellipse(x + 200, y, radiusX, radiusY, rotation, 0, 2 * Math.PI, false); 53 | ctx.stroke(); 54 | ctx.beginPath(); 55 | ctx.ellipse(x, y + 200, radiusX, radiusY, rotation, 0, 2 * Math.PI, false); 56 | ctx.stroke(); 57 | ctx.beginPath(); 58 | ctx.ellipse(x + 200, y + 200, radiusX, radiusY, rotation, 0, 2 * Math.PI, false); 59 | ctx.stroke(); 60 | 61 | ctx.strokeStyle = 'red'; 62 | ctx.beginPath(); 63 | ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, false); 64 | ctx.stroke(); 65 | ctx.beginPath(); 66 | ctx.ellipse(x + 200, y, radiusX, radiusY, rotation, startAngle, endAngle, true); 67 | ctx.stroke(); 68 | ctx.beginPath(); 69 | ctx.ellipse(x, y + 200, radiusX, radiusY, rotation, endAngle, startAngle, false); 70 | ctx.stroke(); 71 | ctx.beginPath(); 72 | ctx.ellipse(x + 200, y + 200, radiusX, radiusY, rotation, endAngle, startAngle, true); 73 | ctx.stroke(); 74 | 75 | ctx.stroke(); 76 | canvas.vgSwapBuffers(); 77 | 78 | eu.handleTermination(); 79 | eu.waitForInput(); 80 | -------------------------------------------------------------------------------- /examples/fonts.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(800, 800); 8 | var ctx = canvas.getContext('2d'); 9 | 10 | var eu = require('./util'); 11 | 12 | var growthInterval = 50; 13 | var growthRate = 1.1; 14 | var rotationStep = -Math.PI / 180 * 5; 15 | var textSize = 40; 16 | var fullText = []; 17 | var lastTime = 0; 18 | 19 | var i = 0; 20 | function text(time) { 21 | if (Math.floor(time / growthInterval) <= lastTime) return; 22 | 23 | textSize *= growthRate; 24 | lastTime = Math.floor(time / growthInterval); 25 | 26 | ctx.resetTransform(); 27 | ctx.translate(canvas.width / 2, canvas.height / 2); 28 | ctx.fillStyle = 'rgba(255, 255, 255, 0.5)'; 29 | 30 | ctx.rotate(i * rotationStep); 31 | ctx.font = 'normal ' + Math.round(textSize) + 'px serif'; 32 | ctx.fillText('Abracadabra', 0, 0); 33 | 34 | ctx.beginPath(); 35 | ctx.moveTo(-5, -5); 36 | ctx.lineTo(+5, +5); 37 | ctx.moveTo(-5, +5); 38 | ctx.lineTo(+5, -5); 39 | ctx.stroke(); 40 | 41 | i++; 42 | } 43 | 44 | ctx.resetTransform(); 45 | ctx.clearRect(0, 0, canvas.width, canvas.height); 46 | canvas.vgSwapBuffers(); 47 | ctx.strokeStyle = 'red'; 48 | 49 | eu.animate(function (time) { 50 | text(time); 51 | }); 52 | 53 | eu.handleTermination(); 54 | 55 | eu.waitForInput(); 56 | -------------------------------------------------------------------------------- /examples/getImageData.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global Image: true */ 4 | "use strict"; 5 | 6 | var Canvas = require('../lib/canvas'); 7 | var Image = Canvas.Image; 8 | var canvas = new Canvas(200, 200); 9 | var ctx = canvas.getContext('2d'); 10 | var fs = require('fs'); 11 | 12 | var eu = require('./util'); 13 | var shapes = require('./shapes'); 14 | var squareSize = 120; 15 | 16 | ctx.clearRect(0, 0, canvas.width, canvas.height); 17 | 18 | ctx.fillRect(0, 0, 150, 150); 19 | 20 | ctx.fillStyle = '#ff8000'; 21 | shapes.drawSquare(ctx, squareSize); 22 | 23 | ctx.save(); 24 | ctx.translate(0, canvas.height - squareSize); 25 | ctx.fillStyle = '#0080ff'; 26 | shapes.drawSquare(ctx, squareSize); 27 | ctx.restore(); 28 | 29 | ctx.save(); 30 | ctx.translate(canvas.width - squareSize, canvas.height - squareSize); 31 | ctx.fillStyle = '#8000ff'; 32 | shapes.drawSquare(ctx, squareSize); 33 | ctx.restore(); 34 | 35 | ctx.save(); 36 | ctx.translate(canvas.width - squareSize, 0); 37 | ctx.fillStyle = '#ff0080'; 38 | shapes.drawSquare(ctx, squareSize); 39 | ctx.restore(); 40 | 41 | var img = ctx.getImageData(0, 0, 120, 120); 42 | ctx.putImageData(img, 200, 200); 43 | ctx.putImageData(img, 400, 200); 44 | ctx.putImageData(img, 200, 400); 45 | ctx.putImageData(img, 400, 400); 46 | 47 | canvas.vgSwapBuffers(); 48 | eu.waitForInput(); 49 | -------------------------------------------------------------------------------- /examples/globalAlpha.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/globalAlpha.js 7 | 8 | var eu = require('./util'); 9 | 10 | var Canvas = require('../lib/canvas'); 11 | var canvas = new Canvas(150, 150); 12 | var ctx = canvas.getContext('2d'); 13 | var eu = require('./util'); 14 | var fs = require('fs'); 15 | 16 | ctx.clearRect(0, 0, canvas.width, canvas.height); 17 | 18 | ctx.fillStyle = '#FD0'; 19 | ctx.fillRect(0, 0, 75, 75); 20 | ctx.fillStyle = '#6C0'; 21 | ctx.fillRect(75, 0, 75, 75); 22 | ctx.fillStyle = '#09F)'; 23 | ctx.fillRect(0, 75, 75, 75); 24 | ctx.fillStyle = '#F30'; 25 | ctx.fillRect(75, 75, 150, 150); 26 | ctx.fillStyle = '#FFF'; 27 | 28 | // set transparency value 29 | ctx.globalAlpha = 0.2; 30 | 31 | // Draw semi transparent circles 32 | for (var i = 0; i < 7; i++) { 33 | ctx.beginPath(); 34 | ctx.arc(75, 75, 10 + 10 * i, 0, Math.PI * 2, false); 35 | ctx.fill(); 36 | } 37 | 38 | eu.saveScreenshot(ctx, 0, 0, 150, 150, 39 | 'examples/screenshots/globalAlpha.png'); 40 | 41 | canvas.vgSwapBuffers(); 42 | eu.handleTermination(); 43 | eu.waitForInput(); 44 | -------------------------------------------------------------------------------- /examples/glyphs.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/pango-glyphs.js 7 | 8 | var Canvas = require('../lib/canvas'); 9 | var canvas = new Canvas(400, 100); 10 | var ctx = canvas.getContext('2d'); 11 | var fs = require('fs'); 12 | 13 | var eu = require('./util'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | ctx.globalAlpha = 1; 18 | ctx.font = 'normal 16px Impact'; 19 | 20 | ctx.textBaseline = 'top'; 21 | 22 | // Note this demo depends node-canvas being installed with pango support, 23 | // and your system having installed fonts supporting the glyphs. 24 | 25 | //////ctx.fillStyle = '#000'; 26 | ctx.fillStyle = '#fff'; 27 | ctx.fillText("English: Some text in Impact.", 10, 10); 28 | ctx.fillText("Japanese: 図書館の中では、静かにする。", 10, 30); 29 | ctx.fillText("Arabic: اللغة العربية هي أكثر اللغات تحدثا ضمن", 10, 50); 30 | ctx.fillText("Korean: 모타는사라미 못하는 사람이", 10, 70); 31 | 32 | canvas.vgSwapBuffers(); 33 | eu.handleTermination(); 34 | eu.waitForInput(); 35 | -------------------------------------------------------------------------------- /examples/gradients.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var Canvas = require('../lib/canvas'); 6 | var canvas = new Canvas(320, 320); 7 | var ctx = canvas.getContext('2d'); 8 | var fs = require('fs'); 9 | 10 | var eu = require('./util'); 11 | 12 | ctx.clearRect(0, 0, canvas.width, canvas.height); 13 | ctx.scale(3, 3); 14 | 15 | // Create gradients 16 | var lingrad = ctx.createLinearGradient(0, 0, 0, 150); 17 | lingrad.addColorStop(0, '#00ABEB'); 18 | lingrad.addColorStop(0.5, '#fff'); 19 | lingrad.addColorStop(0.5, '#26C000'); 20 | lingrad.addColorStop(1, '#fff'); 21 | 22 | var lingrad2 = ctx.createLinearGradient(0, 50, 0, 95); 23 | lingrad2.addColorStop(0.5, '#000'); 24 | lingrad2.addColorStop(1, 'rgba(0,0,0,0)'); 25 | 26 | // assign gradients to fill and stroke styles 27 | ctx.fillStyle = lingrad; 28 | ctx.strokeStyle = lingrad2; 29 | 30 | // draw shapes 31 | ctx.fillRect(10, 10, 130, 130); 32 | ctx.strokeRect(50, 50, 50, 50); 33 | 34 | // Default gradient stops 35 | ctx.fillStyle = '#008000'; 36 | ctx.fillRect(150, 0, 150, 150); 37 | 38 | lingrad = ctx.createLinearGradient(150, 0, 300, 150); 39 | ctx.fillStyle = lingrad; 40 | ctx.fillRect(160, 10, 130, 130); 41 | 42 | // Radial gradients 43 | ctx.fillStyle = '#a00000'; 44 | ctx.fillRect(0, 150, 150, 150); 45 | 46 | lingrad = ctx.createRadialGradient(30, 180, 50, 30, 180, 100); 47 | lingrad.addColorStop(0, '#00ABEB'); 48 | lingrad.addColorStop(0.5, '#fff'); 49 | lingrad.addColorStop(0.5, '#26C000'); 50 | lingrad.addColorStop(1, '#fff'); 51 | ctx.fillStyle = lingrad; 52 | ctx.fillRect(10, 160, 130, 130); 53 | 54 | canvas.vgSwapBuffers(); 55 | eu.handleTermination(); 56 | eu.waitForInput(); 57 | -------------------------------------------------------------------------------- /examples/imageSource.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global Image: true */ 4 | "use strict"; 5 | 6 | // Original code at: 7 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/image-src.js 8 | 9 | var Canvas = require('../lib/canvas'); 10 | var Image = Canvas.Image; 11 | var canvas = new Canvas(200, 200); 12 | var ctx = canvas.getContext('2d'); 13 | var fs = require('fs'); 14 | 15 | var eu = require('./util'); 16 | var shapes = require('./shapes'); 17 | 18 | var screenCapure = new Image(); 19 | 20 | ctx.clearRect(0, 0, canvas.width, canvas.height); 21 | 22 | function drawSquadron(bx, by) { 23 | shapes.drawColoredSquare(ctx, 120, bx + 0, by + 0, '#00f'); 24 | shapes.drawColoredSquare(ctx, 120, bx + 240, by + 0, '#0f0'); 25 | shapes.drawColoredSquare(ctx, 120, bx + 0, by + 240, '#f00'); 26 | shapes.drawColoredSquare(ctx, 120, bx + 240, by + 240, '#000'); 27 | } 28 | 29 | function firstScreen() { 30 | var grid = fs.readFileSync(__dirname + '/images/grid.gif'); 31 | var img = new Image(); 32 | 33 | drawSquadron(0, 0); 34 | ctx.save(); 35 | 36 | canvas.vgSwapBuffers(); 37 | 38 | console.log('Capturing screen as a PNG...'); 39 | var screenAsBuffer = canvas.toBuffer(); 40 | 41 | console.log('Writing to screenshots dir.'); 42 | fs.writeFileSync('examples/screenshots/imageSource_00.png', screenAsBuffer); 43 | 44 | console.log('Rendering it back to an Image object...'); 45 | screenCapure.src = screenAsBuffer; 46 | 47 | console.log('Done.'); 48 | 49 | // Incomplete image. Don't do anything, including crashing. 50 | ctx.drawImage(img, 0, 0); 51 | 52 | img.src = grid; 53 | ctx.drawImage(img, 64, 64, img.width / 2, img.height / 2); 54 | 55 | eu.saveScreenshot(ctx, 0, 0, canvas.width, canvas.height, 56 | 'examples/screenshots/imageSource_01.png'); 57 | 58 | canvas.vgSwapBuffers(); 59 | eu.waitForInput('Press return to turn off the lights.', secondScreen); 60 | } 61 | 62 | 63 | function secondScreen() { 64 | ctx.clearRect(0, 0, canvas.width, canvas.height); 65 | 66 | canvas.vgSwapBuffers(); 67 | eu.waitForInput('Press return for the grand finale.', thirdScreen); 68 | } 69 | 70 | function thirdScreen() { 71 | ctx.clearRect(0, 0, canvas.width, canvas.height); 72 | ctx.drawImage(screenCapure, 0, 0); 73 | 74 | ctx.restore(); 75 | drawSquadron(120, 120); 76 | 77 | canvas.vgSwapBuffers(); 78 | 79 | eu.saveScreenshot(ctx, 0, 0, canvas.width, canvas.height, 80 | 'examples/screenshots/imageSource_final.png'); 81 | eu.waitForInput(); 82 | } 83 | 84 | firstScreen(); -------------------------------------------------------------------------------- /examples/images/flurry.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/flurry.jpeg -------------------------------------------------------------------------------- /examples/images/grid.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grid.gif -------------------------------------------------------------------------------- /examples/images/grid.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grid.jpeg -------------------------------------------------------------------------------- /examples/images/grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grid.png -------------------------------------------------------------------------------- /examples/images/grid.pxm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grid.pxm -------------------------------------------------------------------------------- /examples/images/grido2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grido2.png -------------------------------------------------------------------------------- /examples/images/grido4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/grido4.png -------------------------------------------------------------------------------- /examples/images/noisy_grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/images/noisy_grid.png -------------------------------------------------------------------------------- /examples/lineDashOffset.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(); 8 | var ctx = canvas.getContext('2d'); 9 | var eu = require('./util'); 10 | 11 | var hh = 25; 12 | 13 | var dashLists = [ 14 | [], 15 | [0, 2, 2, 0], 16 | [2, 2], 17 | [2], 18 | [20, 2, 6, 2], 19 | [0, 5, 5, 0], 20 | [5, 5], 21 | [5], 22 | [20, 2, 4, 2], 23 | [20, 2, 4], 24 | [20, 2, 4, 2, 4, 2], 25 | [20, 2, 4, 2, 4] 26 | ]; 27 | 28 | function dashedLine(x0, y0, x1, y1, dashList) { 29 | ctx.beginPath(); 30 | ctx.setLineDash(dashList); 31 | ctx.moveTo(x0, y0); 32 | ctx.lineTo(x1, y1); 33 | ctx.stroke(); 34 | } 35 | 36 | var startTime = undefined; 37 | var savedScreenshot = false; 38 | function paint(time) { 39 | ctx.fillStyle = 'black'; 40 | ctx.fillRect(0, 0, canvas.width, canvas.height); 41 | 42 | if (startTime) { 43 | ctx.lineDashOffset = (time - startTime) / 20; 44 | } else { 45 | startTime = time; 46 | } 47 | 48 | ctx.strokeStyle = 'white'; 49 | ctx.lineWidth = 2; 50 | for (var i = 0; i < dashLists.length; i++) { 51 | dashedLine(50, 50 + hh * i, 450, 50 + hh * i, dashLists[i]); 52 | } 53 | 54 | if (!savedScreenshot && (time - startTime) > 100) { 55 | eu.saveScreenshot(ctx, 0, 0, 500, 400, 56 | 'examples/screenshots/lineDashOffset.png'); 57 | savedScreenshot = true; 58 | } 59 | } 60 | 61 | eu.animate(paint); 62 | 63 | eu.handleTermination(); 64 | 65 | eu.waitForInput(); 66 | -------------------------------------------------------------------------------- /examples/lineDashState.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(); 8 | var ctx = canvas.getContext('2d'); 9 | var eu = require('./util'); 10 | 11 | var hh = 25; 12 | 13 | var dashLists = [ 14 | [], 15 | [0, 2, 2, 0], 16 | [2, 2], 17 | [2], 18 | [20, 2, 6, 2], 19 | [0, 5, 5, 0], 20 | [5, 5], 21 | [5], 22 | [20, 2, 4, 2], 23 | [20, 2, 4], 24 | [20, 2, 4, 2, 4, 2], 25 | [20, 2, 4, 2, 4] 26 | ]; 27 | 28 | function dashedLine(x0, y0, x1, y1, dashList) { 29 | } 30 | 31 | var startTime = undefined; 32 | var savedScreenshot = false; 33 | function paint() { 34 | ctx.fillStyle = 'black'; 35 | ctx.fillRect(0, 0, canvas.width, canvas.height); 36 | 37 | ctx.strokeStyle = 'white'; 38 | ctx.lineWidth = 2; 39 | 40 | ctx.beginPath(); 41 | ctx.setLineDash([10, 10]); 42 | ctx.moveTo(20, 20); 43 | ctx.lineTo(130, 20); 44 | ctx.stroke(); 45 | 46 | ctx.save(); 47 | 48 | ctx.setLineDash([]); 49 | ctx.strokeStyle = 'red'; 50 | 51 | ctx.beginPath(); 52 | ctx.moveTo(20, 40); 53 | ctx.lineTo(130, 40); 54 | ctx.stroke(); 55 | 56 | ctx.restore(); 57 | 58 | ctx.beginPath(); 59 | ctx.moveTo(20, 60); 60 | ctx.lineTo(130, 60); 61 | ctx.stroke(); 62 | } 63 | 64 | paint(); 65 | eu.saveScreenshot(ctx, 0, 0, 150, 80, 66 | 'examples/screenshots/lineDashState.png'); 67 | 68 | canvas.vgSwapBuffers(); 69 | eu.handleTermination(); 70 | eu.waitForInput(); 71 | -------------------------------------------------------------------------------- /examples/pathObject.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var Path = Canvas.Path; 8 | var canvas = new Canvas(); 9 | var ctx = canvas.getContext('2d'); 10 | var eu = require('./util'); 11 | 12 | function paint() { 13 | ctx.clearRect(0, 0, canvas.width, canvas.height); 14 | 15 | var p = new Path(); 16 | p.moveTo(40, 0); 17 | p.arc(0, 0, 40, 0, Math.PI * 2, false); 18 | 19 | p.moveTo(25, 0); 20 | p.arc(0, 0, 25, 0, Math.PI, false); 21 | 22 | p.moveTo(-10 + 5, -10); 23 | p.arc(-10, -10, 5, 0, Math.PI * 2, false); 24 | p.moveTo(10 + 5, -10); 25 | p.arc(10, -10, 5, 0, Math.PI * 2, false); 26 | 27 | p.addPath(p, new Canvas.SVGMatrix().mTranslate(100, 0)); 28 | p.addPath(p, new Canvas.SVGMatrix().mRotate(Math.PI/2).mTranslate(200, 0)); 29 | 30 | ctx.lineWidth = 5; 31 | 32 | for (var i = 0; i < 200; i++) { 33 | ctx.resetTransform(); 34 | ctx.translate(Math.random() * canvas.width, Math.random() * canvas.height); 35 | ctx.rotate(Math.random() * Math.PI - Math.PI/2); 36 | 37 | ctx.strokeStyle = 'rgba(' + Math.random() * 255 + ',' + 38 | Math.random() * 255 + ',' + Math.random() * 255 + ',' + 39 | Math.random() + ')'; 40 | 41 | ctx.stroke(p); 42 | } 43 | } 44 | 45 | paint(); 46 | 47 | canvas.vgSwapBuffers(); 48 | 49 | eu.handleTermination(); 50 | eu.waitForInput(); 51 | -------------------------------------------------------------------------------- /examples/pathPrimitives.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var Path = Canvas.Path; 8 | var canvas = new Canvas(); 9 | var ctx = canvas.getContext('2d'); 10 | var eu = require('./util'); 11 | 12 | var dotRadius = 5; 13 | 14 | var dotPath = new Path(); 15 | var linePath = new Path(); 16 | var pathPath = new Path(); 17 | 18 | function dot(x, y) { 19 | dotPath.moveTo(x + dotRadius, y); 20 | dotPath.arc(x, y, dotRadius, 0, 2 * Math.PI, false); 21 | } 22 | 23 | function segmentDraft(x0, y0, x1, y1, d0, d1) { 24 | if (d0 === undefined) d0 = 20; 25 | if (d1 === undefined) d1 = d0; 26 | var vX = x1 - x0; 27 | var vY = y1 - y0; 28 | var modV = Math.sqrt(vX * vX + vY * vY); 29 | vX /= modV; 30 | vY /= modV; 31 | 32 | linePath.moveTo(x0 - d0 * vX, y0 - d0 * vY); 33 | linePath.lineTo(x1 + d1 * vX, y1 + d1 * vY); 34 | } 35 | 36 | function lineToDraft(x0, y0, x1, y1) { 37 | segmentDraft(x0, y0, x1, y1); 38 | 39 | dot(x0, y0); 40 | dot(x1, y1); 41 | 42 | pathPath.moveTo(x0, y0); 43 | pathPath.lineTo(x1, y1); 44 | } 45 | 46 | function quadraticCurveToDraft(x0, y0, x1, y1, x2, y2) { 47 | segmentDraft(x0, y0, x1, y1, 20, 10); 48 | segmentDraft(x1, y1, x2, y2, 10, 20); 49 | 50 | dot(x0, y0); 51 | dot(x1, y1); 52 | dot(x2, y2); 53 | 54 | pathPath.moveTo(x0, y0); 55 | pathPath.quadraticCurveTo(x1, y1, x2, y2); 56 | } 57 | 58 | function bezierCurveToDraft(x0, y0, x1, y1, x2, y2, x3, y3) { 59 | segmentDraft(x0, y0, x1, y1, 20, 10); 60 | segmentDraft(x1, y1, x2, y2, 10, 10); 61 | segmentDraft(x2, y2, x3, y3, 10, 20); 62 | 63 | dot(x0, y0); 64 | dot(x1, y1); 65 | dot(x2, y2); 66 | dot(x3, y3); 67 | 68 | pathPath.moveTo(x0, y0); 69 | pathPath.bezierCurveTo(x1, y1, x2, y2, x3, y3); 70 | } 71 | 72 | function arcToDraft(x0, y0, x1, y1, x2, y2, rx, ry, rotation) { 73 | segmentDraft(x0, y0, x1, y1, 20, 0); 74 | segmentDraft(x1, y1, x2, y2, 0, 20); 75 | 76 | dot(x0, y0); 77 | dot(x1, y1); 78 | dot(x2, y2); 79 | 80 | pathPath.moveTo(x0, y0); 81 | pathPath.arcTo(x1, y1, x2, y2, rx, ry, rotation); 82 | } 83 | 84 | function arcDraft(x0, y0, x1, y1, r, startAngle, endAngle, anticlockwise) { 85 | ellipseDraft(x0, y0, x1, y1, r, r, 0, startAngle, endAngle, anticlockwise); 86 | } 87 | 88 | function ellipseDraft(x0, y0, x1, y1, rX, rY, rotation, startAngle, endAngle, anticlockwise) { 89 | dot(x0, y0); 90 | dot(x1, y1); 91 | 92 | pathPath.moveTo(x0, y0); 93 | pathPath.ellipse(x1, y1, rX, rY, rotation, startAngle, endAngle, anticlockwise); 94 | } 95 | 96 | function rectDraft(x, y, w, h) { 97 | segmentDraft(x, y, x + w, y, 20, 10); 98 | segmentDraft(x, y, x, y + h, 20, 10); 99 | segmentDraft(x + w, y, x + w, y + h, 10, 20); 100 | segmentDraft(x, y + h, x + w, y + h, 10, 20); 101 | 102 | dot(x, y); 103 | dot(x + w, y + h); 104 | 105 | pathPath.rect(x, y, w, h); 106 | } 107 | 108 | function paint() { 109 | ctx.clearRect(0, 0, canvas.width, canvas.height); 110 | 111 | var bx, by, grid = 200, inset = 50, width = grid - 2 * inset; 112 | 113 | bx = grid * 0; by = grid * 0 + grid / 2; 114 | lineToDraft(bx + inset, by, bx + grid - inset, by); 115 | 116 | bx = grid * 0; by = grid * 1 + grid / 2; 117 | quadraticCurveToDraft(bx + inset , by + width / 4, 118 | bx + grid / 2 , by - width / 4, 119 | bx + grid - inset, by + width / 4); 120 | 121 | bx = grid * 0; by = grid * 2 + grid / 2; 122 | bezierCurveToDraft(bx + inset , by, 123 | bx + inset + width / 4, by - width / 4, 124 | bx + inset + 3 * width / 4, by + width / 4, 125 | bx + grid - inset , by); 126 | 127 | bx = grid * 0; by = grid * 3 + 2 * width / 3; 128 | bezierCurveToDraft(bx + inset , by, 129 | bx + inset , by - 2 * width / 3, 130 | bx + grid - inset, by - 2 * width / 3, 131 | bx + grid - inset, by); 132 | 133 | bx = grid * 1; by = grid * 0 + grid / 2; 134 | arcToDraft(bx + inset , by - width / 2, 135 | bx + grid - inset, by - width / 2, 136 | bx + grid - inset, by + width / 2, 137 | width / 4); 138 | 139 | bx = grid * 1; by = grid * 1 + grid / 2; 140 | arcToDraft(bx + inset , by - width / 2, 141 | bx + grid - inset, by - width / 2, 142 | bx + grid - inset, by + width / 2, 143 | width / 2, width / 4, 0); 144 | 145 | bx = grid * 1; by = grid * 2 + grid / 2; 146 | arcToDraft(bx + inset , by - width / 2, 147 | bx + grid - inset, by - width / 2, 148 | bx + grid - inset, by + width / 2, 149 | width / 2, width / 4, Math.PI / 6); 150 | 151 | bx = grid * 2; by = grid * 0; 152 | arcDraft(bx + grid - inset, by + inset, 153 | bx + grid / 2, by + grid / 2, 154 | width / 3, 155 | 0, Math.PI, 156 | false); 157 | 158 | bx = grid * 2; by = grid * 1; 159 | arcDraft(bx + grid - inset, by + inset, 160 | bx + grid / 2, by + grid / 2, 161 | width / 3, 162 | 0, Math.PI, 163 | true); 164 | 165 | bx = grid * 2; by = grid * 2; 166 | arcDraft(bx + grid / 2 + width / 3, by + grid / 2, 167 | bx + grid / 2, by + grid / 2, 168 | width / 3, 169 | 0, Math.PI / 2, 170 | false); 171 | 172 | bx = grid * 2; by = grid * 3; 173 | arcDraft(bx + grid / 2 + width / 3, by + grid / 2, 174 | bx + grid / 2, by + grid / 2, 175 | width / 3, 176 | 0, Math.PI / 2, 177 | true); 178 | 179 | 180 | bx = grid * 3; by = grid * 0; 181 | ellipseDraft(bx + grid - inset, by + inset, 182 | bx + grid / 2, by + grid / 2, 183 | width / 3, width / 6, 0, 184 | 0, Math.PI, 185 | false); 186 | 187 | bx = grid * 3; by = grid * 1; 188 | ellipseDraft(bx + grid - inset, by + inset, 189 | bx + grid / 2, by + grid / 2, 190 | width / 3, width / 6, 0, 191 | 0, Math.PI, 192 | true); 193 | 194 | bx = grid * 3; by = grid * 2; 195 | ellipseDraft(bx + grid / 2 + width / 3, by + grid / 2, 196 | bx + grid / 2, by + grid / 2, 197 | width / 3, width / 6, 0, 198 | 0, Math.PI / 2, 199 | false); 200 | 201 | bx = grid * 3; by = grid * 3; 202 | ellipseDraft(bx + grid / 2 + width / 3, by + grid / 2, 203 | bx + grid / 2, by + grid / 2, 204 | width / 3, width / 6, 0, 205 | 0, Math.PI / 2, 206 | true); 207 | 208 | bx = grid * 4; by = grid * 0; 209 | ellipseDraft(bx + grid - inset, by + inset, 210 | bx + grid / 2, by + grid / 2, 211 | width / 3, width / 6, Math.PI / 6, 212 | 0, Math.PI, 213 | false); 214 | 215 | bx = grid * 4; by = grid * 1; 216 | ellipseDraft(bx + grid - inset, by + inset, 217 | bx + grid / 2, by + grid / 2, 218 | width / 3, width / 6, Math.PI / 6, 219 | 0, Math.PI, 220 | true); 221 | 222 | bx = grid * 4; by = grid * 2; 223 | ellipseDraft(bx + grid / 2 + width / 3 * Math.cos(Math.PI / 6), 224 | by + grid / 2 + width / 3 * Math.sin(Math.PI / 6), 225 | bx + grid / 2, 226 | by + grid / 2, 227 | width / 3, width / 6, Math.PI / 6, 228 | 0, Math.PI / 2, 229 | false); 230 | 231 | bx = grid * 4; by = grid * 3; 232 | ellipseDraft(bx + grid / 2 + width / 3 * Math.cos(Math.PI / 6), 233 | by + grid / 2 + width / 3 * Math.sin(Math.PI / 6), 234 | bx + grid / 2, 235 | by + grid / 2, 236 | width / 3, width / 6, Math.PI / 6, 237 | 0, Math.PI / 2, 238 | true); 239 | 240 | bx = grid * 5; by = grid * 0; 241 | rectDraft(bx + inset, by + inset, 242 | width, width); 243 | 244 | bx = grid * 5; by = grid * 1; 245 | rectDraft(bx + inset, by + inset, 246 | width, width / 2); 247 | 248 | bx = grid * 5; by = grid * 2; 249 | rectDraft(bx + inset, by + inset, 250 | width / 2, width); 251 | 252 | ctx.lineJoin = 'round'; 253 | 254 | ctx.strokeStyle = 'red'; 255 | ctx.lineWidth = '2'; 256 | ctx.stroke(linePath); 257 | 258 | ctx.fillStyle = 'white'; 259 | ctx.fill(dotPath); 260 | 261 | ctx.globalAlpha = 0.5; 262 | ctx.lineCap = 'round'; 263 | ctx.strokeStyle = '#00de28'; 264 | ctx.lineWidth = 2 * (dotRadius + 2); 265 | ctx.stroke(pathPath); 266 | 267 | ctx.globalAlpha = 1.0; 268 | } 269 | 270 | // ctx.scale(2,2); 271 | paint(); 272 | 273 | canvas.vgSwapBuffers(); 274 | 275 | eu.handleTermination(); 276 | eu.waitForInput(); 277 | -------------------------------------------------------------------------------- /examples/pathText.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var DrawingStyle = Canvas.DrawingStyle; 8 | var SVGMatrix = Canvas.SVGMatrix; 9 | var Path = Canvas.Path; 10 | var canvas = new Canvas(); 11 | var ctx = canvas.getContext('2d'); 12 | var eu = require('./util'); 13 | 14 | var textRendering = require('../lib/text/rendering'); 15 | 16 | var dotRadius = 3; 17 | var dotPath = new Path(); 18 | 19 | function dot(x, y) { 20 | dotPath.moveTo(x + dotRadius, y); 21 | dotPath.arc(x, y, dotRadius, 0, 2 * Math.PI, false); 22 | } 23 | 24 | var style = new DrawingStyle(); 25 | function pathText(text, x, y, textAlign, textBaseline) { 26 | ctx.strokeStyle = 'red'; 27 | ctx.lineWidth = 2; 28 | 29 | var baseline = new Path(); 30 | baseline.moveTo(x, y); 31 | baseline.ellipse(x + 100, y, 100, 50, 0, Math.PI, 2 * Math.PI, false); 32 | ctx.stroke(baseline); 33 | 34 | if (textAlign === 'left') { 35 | dot(x, y); 36 | } else if (textAlign === 'right') { 37 | dot(x + 200, y); 38 | } else if (textAlign === 'center') { 39 | dot(x + 100, y - 50); 40 | } 41 | 42 | var p = new Path(); 43 | style.textAlign = textAlign; 44 | style.textBaseline = textBaseline; 45 | p.addText(text, style, new SVGMatrix(), baseline); 46 | ctx.fill(p); 47 | 48 | p.destroy(); 49 | baseline.destroy(); 50 | } 51 | 52 | function paint() { 53 | ctx.fillStyle = 'black'; 54 | ctx.fillRect(0, 0, canvas.width, canvas.height); 55 | var font = '30px sans-serif'; 56 | 57 | ctx.textAlign = 'left'; 58 | ctx.font = font; 59 | var text = 'Awesome Text is Awesome!'; 60 | var metrics = ctx.measureText(text); 61 | 62 | ctx.fillStyle = 'rgb(64,192,255)'; 63 | ctx.moveTo(100 - metrics.actualBoundingBoxLeft - 30, 64 | 100 - metrics.actualBoundingBoxAscent - 30); 65 | ctx.lineTo(100 - metrics.actualBoundingBoxLeft - 30, 66 | 100 - 30 + metrics.actualBoundingBoxDescent + 60); 67 | ctx.lineTo(100 - 30 + metrics.actualBoundingBoxRight + 60, 68 | 100 - 30 + metrics.actualBoundingBoxDescent + 60); 69 | ctx.lineTo(100 - 30 + metrics.actualBoundingBoxRight + 60, 70 | 100 - 30 + metrics.actualBoundingBoxDescent + 50); 71 | ctx.lineTo(100 - metrics.actualBoundingBoxLeft - 20, 72 | 100 - metrics.actualBoundingBoxAscent - 30); 73 | ctx.closePath(); 74 | ctx.fill(); 75 | 76 | style.font = font; 77 | 78 | var transform = new SVGMatrix(); 79 | 80 | var p = new Path(); 81 | 82 | p.rect(100 - metrics.actualBoundingBoxLeft - 20, 83 | 100 - metrics.actualBoundingBoxAscent - 20, 84 | metrics.actualBoundingBoxRight + metrics.actualBoundingBoxLeft + 40, 85 | metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent + 40); 86 | 87 | p.addText(text, style, transform, 100, 100); 88 | 89 | ctx.fillStyle = 'rgb(0,128,255)'; 90 | 91 | ctx.fill(p); 92 | 93 | text = 'ellipse'; // Just something with ascenders and descenders 94 | var xx = 100, yy = 300; 95 | ['bottom', 'alphabetical', 'middle', 'top'].map(function (textBaseline, baseIdx) { 96 | ['left', 'center', 'right'].map(function (textAlign, alignIdx) { 97 | pathText(text, xx + alignIdx * 300, yy + baseIdx * 200, textAlign, textBaseline); 98 | }); 99 | }); 100 | 101 | ctx.fillStyle = 'white'; 102 | ctx.fill(dotPath); 103 | } 104 | 105 | paint(); 106 | 107 | canvas.vgSwapBuffers(); 108 | 109 | eu.saveScreenshot(ctx, 0, 0, canvas.height, canvas.height, 110 | 'examples/screenshots/pathText.png'); 111 | 112 | eu.handleTermination(); 113 | eu.waitForInput(); 114 | -------------------------------------------------------------------------------- /examples/patterns.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global requestAnimationFrame: true, Image: true */ 4 | "use strict"; 5 | 6 | var util = require('util'); 7 | var Canvas = require('../lib/canvas'); 8 | var Image = Canvas.Image; 9 | var canvas = new Canvas(); 10 | var ctx = canvas.getContext('2d'); 11 | var eu = require('./util'); 12 | var fs = require('fs'); 13 | 14 | var width = 500, height = 400; 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | var pattern_no_repeat, pattern_repeat, pattern_repeat_x, pattern_repeat_y; 18 | 19 | var image = new Image(); 20 | var image_ready = true; 21 | 22 | image.src = fs.readFileSync(__dirname + '/images/grido4.png'); 23 | pattern_no_repeat = ctx.createPattern(image, 'no-repeat'); 24 | pattern_repeat = ctx.createPattern(image, 'repeat'); 25 | pattern_repeat_x = ctx.createPattern(image, 'repeat-x'); 26 | pattern_repeat_y = ctx.createPattern(image, 'repeat-y'); 27 | 28 | function paintRect(x, y, w, h, paintFn) { 29 | ctx.beginPath(); 30 | ctx.rect(x, y, w, h); 31 | paintFn(); 32 | } 33 | 34 | function fill() { 35 | ctx.fill(); 36 | } 37 | 38 | function stroke() { 39 | ctx.stroke(); 40 | } 41 | 42 | function fillRects(x, y) { 43 | ctx.fillStyle = pattern_no_repeat; 44 | paintRect(x + 10, y + 10, 90, 90, fill); 45 | ctx.fillStyle = pattern_repeat_x; 46 | paintRect(x + 100, y + 10, 90, 90, fill); 47 | ctx.fillStyle = pattern_repeat_y; 48 | paintRect(x + 10, y + 100, 90, 90, fill); 49 | ctx.fillStyle = pattern_repeat; 50 | paintRect(x + 100, y + 100, 90, 90, fill); 51 | } 52 | 53 | function strokeRects(x, y) { 54 | ctx.strokeStyle = pattern_no_repeat; 55 | paintRect(x + 10, y + 10, 90, 90, stroke); 56 | ctx.strokeStyle = pattern_repeat_x; 57 | paintRect(x + 100, y + 10, 90, 90, stroke); 58 | ctx.strokeStyle = pattern_repeat_y; 59 | paintRect(x + 10, y + 100, 90, 90, stroke); 60 | ctx.strokeStyle = pattern_repeat; 61 | paintRect(x + 100, y + 100, 90, 90, stroke); 62 | } 63 | 64 | var startTime = undefined; 65 | function paint(time) { 66 | var delta = undefined; 67 | ctx.fillStyle = 'black'; 68 | ctx.fillRect(0, 0, canvas.width, canvas.height); 69 | 70 | if (startTime) { 71 | delta = (time - startTime) / 20; 72 | } else { 73 | startTime = time; 74 | return; 75 | } 76 | 77 | if (!image_ready) { 78 | return; 79 | } 80 | 81 | ctx.translate(100, 0); 82 | ctx.rotate(Math.PI / 6); 83 | 84 | fillRects(0, 0, fill); 85 | 86 | ctx.translate(200, 0); 87 | ctx.lineWidth = 10; 88 | strokeRects(0, 0, fill); 89 | ctx.translate(-200, 0); 90 | 91 | ctx.rotate(-Math.PI / 6); 92 | ctx.translate(-100, 0); 93 | } 94 | 95 | function animate(t) { 96 | paint(t); 97 | requestAnimationFrame(animate); 98 | } 99 | 100 | animate(); 101 | -------------------------------------------------------------------------------- /examples/ray.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/ray.js 7 | 8 | var Canvas = require('../lib/canvas'); 9 | var canvas = new Canvas(243 * 4, 243); 10 | var ctx = canvas.getContext('2d'); 11 | var fs = require('fs'); 12 | 13 | var eu = require('./util'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | function render(level) { 18 | ctx.fillStyle = getPointColour(122, 122); 19 | ctx.fillRect(0, 0, 240, 240); 20 | renderLevel(level, 81, 0); 21 | } 22 | 23 | function renderLevel(minimumLevel, level, y) { 24 | var x; 25 | for (x = 0; x < 243 / level; ++x) { 26 | drawBlock(x, y, level); 27 | } 28 | for (x = 0; x < 243 / level; x += 3) { 29 | drawBlock(x, y + 1, level); 30 | drawBlock(x + 2, y + 1, level); 31 | } 32 | for (x = 0; x < 243 / level; ++x) { 33 | drawBlock(x, y + 2, level); 34 | } 35 | if ((y += 3) >= 243 / level) { 36 | y = 0; 37 | level /= 3; 38 | } 39 | if (level >= minimumLevel) { 40 | renderLevel(minimumLevel, level, y); 41 | } 42 | } 43 | 44 | function drawBlock(x, y, level) { 45 | ctx.fillStyle = getPointColour(x * level + (level - 1) / 2, 46 | y * level + (level - 1) / 2); 47 | 48 | ctx.fillRect(x * level, 49 | y * level, 50 | level, 51 | level); 52 | } 53 | 54 | function getPointColour(x, y) { 55 | x = x / 121.5 - 1; 56 | y = -y / 121.5 + 1; 57 | var x2y2 = x * x + y * y; 58 | if (x2y2 > 1) { 59 | return '#000'; 60 | } else { 61 | var root = Math.sqrt(1 - x2y2); 62 | var x3d = x * 0.7071067812 + root / 2 - y / 2; 63 | var y3d = x * 0.7071067812 - root / 2 + y / 2; 64 | var z3d = 0.7071067812 * root + 0.7071067812 * y; 65 | var brightness = -x / 2 + root * 0.7071067812 + y / 2; 66 | if (brightness < 0) brightness = 0; 67 | return 'rgb(' + Math.round(brightness * 127.5 * (1 - y3d)) + 68 | ',' + Math.round(brightness * 127.5 * (x3d + 1)) + 69 | ',' + Math.round(brightness * 127.5 * (z3d + 1)) + 70 | ')'; 71 | } 72 | } 73 | 74 | var start = new Date(); 75 | render(10); 76 | ctx.translate(243, 0); 77 | render(6); 78 | ctx.translate(243, 0); 79 | render(3); 80 | ctx.translate(243, 0); 81 | render(1); 82 | console.log('Rendered in %s seconds', (new Date() - start) / 1000); 83 | 84 | canvas.vgSwapBuffers(); 85 | eu.handleTermination(); 86 | eu.waitForInput(); 87 | -------------------------------------------------------------------------------- /examples/screenshots/clipImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/clipImage.png -------------------------------------------------------------------------------- /examples/screenshots/clipping.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/clipping.png -------------------------------------------------------------------------------- /examples/screenshots/clippingState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/clippingState.png -------------------------------------------------------------------------------- /examples/screenshots/color.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/color.png -------------------------------------------------------------------------------- /examples/screenshots/drawImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/drawImage.png -------------------------------------------------------------------------------- /examples/screenshots/drawImageAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/drawImageAlpha.png -------------------------------------------------------------------------------- /examples/screenshots/globalAlpha.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/globalAlpha.png -------------------------------------------------------------------------------- /examples/screenshots/imageSource_00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/imageSource_00.png -------------------------------------------------------------------------------- /examples/screenshots/imageSource_01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/imageSource_01.png -------------------------------------------------------------------------------- /examples/screenshots/imageSource_final.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/imageSource_final.png -------------------------------------------------------------------------------- /examples/screenshots/lineDashOffset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/lineDashOffset.png -------------------------------------------------------------------------------- /examples/screenshots/lineDashState.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/lineDashState.png -------------------------------------------------------------------------------- /examples/screenshots/pathText.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/pathText.png -------------------------------------------------------------------------------- /examples/screenshots/shadows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/examples/screenshots/shadows.png -------------------------------------------------------------------------------- /examples/shadows.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | /*global requestAnimationFrame: true, Image: true */ 4 | "use strict"; 5 | 6 | var util = require('util'); 7 | var Canvas = require('../lib/canvas'); 8 | var Image = Canvas.Image; 9 | var canvas = new Canvas(); 10 | var ctx = canvas.getContext('2d'); 11 | var eu = require('./util'); 12 | var fs = require('fs'); 13 | 14 | var width = 380, height = 270; 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | var background = new Image(); 18 | var grid = new Image(); 19 | background.src = fs.readFileSync(__dirname + '/images/noisy_grid.png'); 20 | grid.src = fs.readFileSync(__dirname + '/screenshots/clipImage.png'); 21 | var pattern = ctx.createPattern(background, 'repeat'); 22 | 23 | function paint() { 24 | ctx.fillStyle = pattern; 25 | ctx.fillRect(0, 0, width, height); 26 | 27 | ctx.save(); 28 | 29 | ctx.shadowColor = 'rgba(0,0,0,1.0)'; 30 | ctx.shadowBlur = 5; 31 | ctx.shadowOffsetX = 2; 32 | ctx.shadowOffsetY = 2; 33 | 34 | var x, y; 35 | 36 | y = 20; 37 | 38 | ctx.fillStyle = 'rgba(255,0,0,0.5)'; 39 | ctx.fillRect(20, y, 40, 40); 40 | ctx.beginPath(); 41 | ctx.rect(20 + 60, y, 40, 40); 42 | ctx.fill(); 43 | 44 | ctx.fillStyle = 'rgba(0,255,0,0.5)'; 45 | ctx.fillRect(140, y, 40, 40); 46 | ctx.beginPath(); 47 | ctx.rect(140 + 60, y, 40, 40); 48 | ctx.fill(); 49 | 50 | ctx.fillStyle = 'rgba(0,0,255,0.5)'; 51 | ctx.fillRect(260, y, 40, 40); 52 | ctx.beginPath(); 53 | ctx.rect(260 + 60, y, 40, 40); 54 | ctx.fill(); 55 | 56 | y += 60; 57 | 58 | ctx.strokeStyle = 'rgba(255,0,0,0.5)'; 59 | ctx.strokeRect(20, y, 100, 40); 60 | 61 | ctx.strokeStyle = 'rgba(0,255,0,0.5)'; 62 | ctx.strokeRect(140, y, 100, 40); 63 | 64 | ctx.strokeStyle = 'rgba(0,0,255,0.5)'; 65 | ctx.strokeRect(260, y, 100, 40); 66 | 67 | ctx.font = '20px sans-serif'; 68 | y += 80; 69 | 70 | ctx.fillStyle = 'rgba(255,0,0,0.5)'; 71 | ctx.fillText('Shadows', 20, y); 72 | 73 | ctx.fillStyle = 'rgba(0,255,0,0.5)'; 74 | ctx.fillText('Shadows', 140, y); 75 | 76 | ctx.fillStyle = 'rgba(0,0,255,0.5)'; 77 | ctx.fillText('Shadows', 260, y); 78 | 79 | y += 20; 80 | ctx.drawImage(grid, 20 + 18, y); 81 | ctx.drawImage(grid, 140 + 18, y); 82 | 83 | ctx.restore(); 84 | 85 | ctx.drawImage(grid, 260 + 18, y); 86 | } 87 | 88 | paint(); 89 | 90 | canvas.vgSwapBuffers(); 91 | 92 | eu.saveScreenshot(ctx, 0, 0, width, height, 93 | 'examples/screenshots/shadows.png'); 94 | console.log('Screenshot taken.'); 95 | 96 | eu.handleTermination(); 97 | eu.waitForInput(); 98 | -------------------------------------------------------------------------------- /examples/shapes.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var shapes = module.exports; 5 | 6 | var drawSquare = shapes.drawSquare = function (ctx, squareSize) { 7 | var step = squareSize / 8; 8 | var offsetStep = squareSize / 16; 9 | var offset = 0; 10 | var size = squareSize; 11 | 12 | ctx.save(); 13 | 14 | ctx.fillRect(offset, offset, size, size); 15 | offset += offsetStep; 16 | size -= 2 * step; 17 | 18 | ctx.save(); 19 | ctx.fillStyle = '#FFF'; 20 | ctx.globalAlpha = 0.5; 21 | ctx.fillRect(offset, offset, size, size); 22 | offset += offsetStep; 23 | size -= 2 * step; 24 | 25 | ctx.restore(); 26 | ctx.fillRect(offset, offset, size, size); 27 | offset += offsetStep; 28 | size -= 2 * step; 29 | 30 | ctx.restore(); 31 | ctx.fillRect(offset, offset, size, size); 32 | offset += offsetStep; 33 | size -= 2 * step; 34 | }; 35 | 36 | var drawColoredSquare = shapes.drawColoredSquare = function (ctx, squareSize, x, y, color) { 37 | ctx.save(); 38 | ctx.translate(x, y); 39 | ctx.fillStyle = color; 40 | drawSquare(ctx, squareSize); 41 | ctx.restore(); 42 | }; 43 | -------------------------------------------------------------------------------- /examples/spark.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/spark.js 7 | 8 | var Canvas = require('../lib/canvas'); 9 | var canvas = new Canvas(40, 15); 10 | var ctx = canvas.getContext('2d'); 11 | var fs = require('fs'); 12 | 13 | var eu = require('./util'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | 17 | Object.defineProperty(Array.prototype, 'max', { 18 | get: function () { 19 | var max = 0; 20 | for (var i = 0, len = this.length; i < len; ++i) { 21 | var n = this[i]; 22 | if (n > max) max = n; 23 | } 24 | return max; 25 | } 26 | }); 27 | 28 | function spark(ctx, data) { 29 | var len = data.length; 30 | var pad = 1; 31 | var width = ctx.canvas.width; 32 | var height = ctx.canvas.height; 33 | var barWidth = width / len; 34 | var max = data.max; 35 | ctx.fillStyle = 'rgba(0,0,255,0.5)'; 36 | ctx.strokeStyle = 'red'; 37 | ctx.lineWidth = 1; 38 | data.forEach(function (n, i) { 39 | var x = i * barWidth + pad; 40 | var y = height * (n / max); 41 | ctx.lineTo(x, height - y); 42 | ctx.fillRect(x, height, barWidth - pad, -y); 43 | }); 44 | ctx.stroke(); 45 | } 46 | 47 | spark(ctx, [1, 2, 4, 5, 10, 4, 2, 5, 4, 3, 3, 2]); 48 | 49 | canvas.vgSwapBuffers(); 50 | eu.handleTermination(); 51 | eu.waitForInput(); 52 | -------------------------------------------------------------------------------- /examples/state.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Original code at: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/state.js 7 | 8 | var Canvas = require('../lib/canvas'); 9 | var canvas = new Canvas(150, 150); 10 | var ctx = canvas.getContext('2d'); 11 | var fs = require('fs'); 12 | 13 | var eu = require('./util'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | ctx.scale(4, 4); 17 | 18 | ctx.fillRect(0, 0, 150, 150); // Draw a rectangle with default settings 19 | ctx.save(); // Save the default state 20 | 21 | ctx.fillStyle = '#09F'; // Make changes to the settings 22 | ctx.fillRect(15, 15, 120, 120); // Draw a rectangle with new settings 23 | 24 | ctx.save(); // Save the current state 25 | ctx.fillStyle = '#FFF'; // Make changes to the settings 26 | ctx.globalAlpha = 0.5; 27 | ctx.fillRect(30, 30, 90, 90); // Draw a rectangle with new settings 28 | 29 | ctx.restore(); // Restore previous state 30 | ctx.fillRect(45, 45, 60, 60); // Draw a rectangle with restored settings 31 | 32 | ctx.restore(); // Restore original state 33 | ctx.fillRect(60, 60, 30, 30); // Draw a rectangle with restored settings 34 | 35 | canvas.vgSwapBuffers(); 36 | eu.handleTermination(); 37 | eu.waitForInput(); 38 | -------------------------------------------------------------------------------- /examples/swissClock.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Based on: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/clock.js 7 | // http://en.wikipedia.org/wiki/Swiss_railway_clock 8 | 9 | var fs = require('fs'); 10 | var util = require('util'); 11 | 12 | var vg = require('openvg'); 13 | var eu = require('./util'); 14 | 15 | var Canvas = require('../lib/canvas'); 16 | var canvas = new Canvas(320, 320); 17 | var ctx = canvas.getContext('2d'); 18 | 19 | function getX(angle) { 20 | return -Math.sin(angle + Math.PI); 21 | } 22 | function getY(angle) { 23 | return Math.cos(angle + Math.PI); 24 | } 25 | 26 | function clock(ctx) { 27 | var now = new Date(); 28 | var i, x, y; 29 | ctx.clearRect(0, 0, canvas.width, canvas.height); 30 | 31 | ctx.save(); 32 | ctx.scale(3, 3); 33 | ctx.translate(canvas.width / 6, canvas.height / 6); 34 | 35 | ctx.beginPath(); 36 | ctx.lineWidth = 14; 37 | ctx.strokeStyle = '#cccccc'; 38 | ctx.fillStyle = '#eeeeee'; 39 | ctx.arc(0, 0, 142, 0, Math.PI * 2, false); 40 | ctx.stroke(); 41 | ctx.fill(); 42 | 43 | ctx.strokeStyle = '#000000'; 44 | ctx.beginPath(); 45 | // Hour marks 46 | ctx.lineWidth = 10; 47 | for (i = 0; i < 12; i++) { 48 | x = getX(Math.PI / 6 * i); 49 | y = getY(Math.PI / 6 * i); 50 | ctx.moveTo(x * 100, y * 100); 51 | ctx.lineTo(x * 125, y * 125); 52 | } 53 | ctx.stroke(); 54 | 55 | // Minute marks 56 | ctx.lineWidth = 3; 57 | ctx.beginPath(); 58 | for (i = 0; i < 60; i++) { 59 | if (i % 5 !== 0) { 60 | x = getX(Math.PI / 30 * i); 61 | y = getY(Math.PI / 30 * i); 62 | ctx.moveTo(x * 117, y * 117); 63 | ctx.lineTo(x * 125, y * 125); 64 | } 65 | } 66 | ctx.stroke(); 67 | 68 | var hr = now.getHours(); 69 | var min = now.getMinutes(); 70 | var sec = now.getSeconds(); 71 | var ms = now.getMilliseconds(); 72 | hr = hr >= 12 ? hr - 12 : hr; 73 | 74 | ctx.fillStyle = "black"; 75 | 76 | // write Hours 77 | x = getX(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec); 78 | y = getY(hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec); 79 | ctx.lineWidth = 14; 80 | ctx.beginPath(); 81 | ctx.moveTo(x * -20, y * -20); 82 | ctx.lineTo(x * 80, y * 80); 83 | ctx.stroke(); 84 | 85 | // write Minutes 86 | x = getX((Math.PI / 30) * min + (Math.PI / 1800) * sec + (Math.PI / 1800000) * ms); 87 | y = getY((Math.PI / 30) * min + (Math.PI / 1800) * sec + (Math.PI / 1800000) * ms); 88 | 89 | ctx.lineWidth = 10; 90 | ctx.beginPath(); 91 | ctx.moveTo(x * -28, y * -28); 92 | ctx.lineTo(x * 120, y * 120); 93 | ctx.stroke(); 94 | 95 | // Write seconds 96 | var rs = sec + ms / 1000; 97 | if (rs >= 58.5) { 98 | x = getX(0); 99 | y = getY(0); 100 | } else { 101 | x = getX((sec + ms / 1000) * 2 * Math.PI / 58.5); 102 | y = getY((sec + ms / 1000) * 2 * Math.PI / 58.5); 103 | } 104 | ctx.strokeStyle = "#D40000"; 105 | ctx.fillStyle = "#D40000"; 106 | ctx.lineWidth = 4; 107 | 108 | ctx.beginPath(); 109 | ctx.moveTo(x * -40, y * -40); 110 | ctx.lineTo(x * 87, y * 87); 111 | ctx.stroke(); 112 | 113 | ctx.beginPath(); 114 | ctx.arc(x * 87, y * 87, 13, 0, Math.PI * 2, false); 115 | ctx.fill(); 116 | ctx.restore(); 117 | } 118 | 119 | eu.animate(function (time) { 120 | clock(ctx); 121 | }); 122 | 123 | eu.handleTermination(); 124 | 125 | eu.waitForInput(); 126 | -------------------------------------------------------------------------------- /examples/textAlign.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var Canvas = require('../lib/canvas'); 6 | var canvas = new Canvas(800, 800); 7 | var ctx = canvas.getContext('2d'); 8 | var fs = require('fs'); 9 | 10 | var eu = require('./util'); 11 | 12 | ctx.clearRect(0, 0, canvas.width, canvas.height); 13 | ctx.strokeStyle = '#fff'; 14 | 15 | ctx.globalAlpha = 0.5; 16 | 17 | ctx.beginPath(); 18 | ctx.moveTo( 80, 600); 19 | ctx.lineTo(720, 600); 20 | 21 | ctx.moveTo(640, 100); 22 | ctx.lineTo(640, 500); 23 | ctx.stroke(); 24 | 25 | ctx.globalAlpha = 1; 26 | ctx.fillStyle = '#ddd'; 27 | ctx.font = '20px serif'; 28 | 29 | ctx.textBaseline = 'middle'; 30 | ctx.textAlign = 'left'; 31 | ctx.fillText('left', 640, 200); 32 | 33 | ctx.textAlign = 'center'; 34 | ctx.fillText('center', 640, 300); 35 | 36 | ctx.textAlign = 'right'; 37 | ctx.fillText('right', 640, 400); 38 | 39 | ctx.textAlign = 'center'; 40 | ctx.textBaseline = 'bottom'; 41 | ctx.fillText('bottom', 160, 600); 42 | 43 | ctx.textBaseline = 'alphabetic'; 44 | ctx.fillText('alpha', 320, 600); 45 | 46 | ctx.textBaseline = 'middle'; 47 | ctx.fillText('middle', 480, 600); 48 | 49 | ctx.textBaseline = 'top'; 50 | ctx.fillText('top', 640, 600); 51 | 52 | ctx.textAlign = 'center'; 53 | ctx.textBaseline = 'alphabetic'; 54 | ctx.font = '80px serif'; 55 | 56 | var m = ctx.measureText("Top"); 57 | var h = m.actualBoundingBoxAscent + m.actualBoundingBoxDescent; 58 | var hw = m.actualBoundingBoxLeft; 59 | var y = 300 + (m.actualBoundingBoxAscent + m.actualBoundingBoxDescent) / 2 - m.actualBoundingBoxDescent; 60 | 61 | ctx.translate(320, y); 62 | ctx.scale(2, 2); 63 | 64 | ctx.globalAlpha = 0.5; 65 | 66 | ctx.lineWidth = 1 / 2; 67 | ctx.beginPath(); 68 | ctx.moveTo(-hw * 1.2, -(m.actualBoundingBoxAscent + m.actualBoundingBoxDescent) / 2 + m.actualBoundingBoxDescent); 69 | ctx.lineTo(+hw * 1.2, -(m.actualBoundingBoxAscent + m.actualBoundingBoxDescent) / 2 + m.actualBoundingBoxDescent); 70 | ctx.moveTo(-hw * 1.2, 0); 71 | ctx.lineTo(+hw * 1.2, 0); 72 | ctx.moveTo(0, -m.actualBoundingBoxAscent - 0.1 * h); 73 | ctx.lineTo(0, m.actualBoundingBoxDescent + 0.1 * h); 74 | ctx.stroke(); 75 | 76 | ctx.lineWidth = 2; 77 | ctx.globalAlpha = 1; 78 | ctx.fillText('Top', 0, 0); 79 | ctx.strokeStyle = '#004080'; 80 | ctx.strokeText('Top', 0, 0); 81 | 82 | ctx.lineWidth = 1 / 2; 83 | ctx.strokeStyle = '#f00'; 84 | ctx.strokeRect(m.actualBoundingBoxLeft, 85 | -m.actualBoundingBoxAscent, 86 | m.actualBoundingBoxRight - m.actualBoundingBoxLeft, 87 | m.actualBoundingBoxAscent + m.actualBoundingBoxDescent); 88 | 89 | ctx.strokeStyle = '#00ff80'; 90 | ctx.beginPath(); 91 | ctx.moveTo(-hw * 1.2, m.emHeightDescent); 92 | ctx.lineTo(+hw * 1.2, m.emHeightDescent); 93 | ctx.stroke(); 94 | 95 | ctx.strokeStyle = '#0080ff'; 96 | ctx.beginPath(); 97 | ctx.moveTo(-hw * 1.2, -m.emHeightAscent); 98 | ctx.lineTo(+hw * 1.2, -m.emHeightAscent); 99 | ctx.stroke(); 100 | 101 | canvas.vgSwapBuffers(); 102 | eu.handleTermination(); 103 | eu.waitForInput(); 104 | -------------------------------------------------------------------------------- /examples/textDrawing.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | // Based on: 6 | // https://github.com/LearnBoost/node-canvas/blob/master/examples/text.js 7 | 8 | var Canvas = require('../lib/canvas'); 9 | var canvas = new Canvas(200, 200); 10 | var ctx = canvas.getContext('2d'); 11 | var fs = require('fs'); 12 | 13 | var eu = require('./util'); 14 | 15 | ctx.clearRect(0, 0, canvas.width, canvas.height); 16 | ctx.scale(4, 4); 17 | ctx.strokeStyle = "#fff"; 18 | 19 | ctx.globalAlpha = 0.2; 20 | 21 | ctx.strokeRect(0, 0, 200, 200); 22 | ctx.lineTo(0, 100); 23 | ctx.lineTo(200, 100); 24 | ctx.stroke(); 25 | 26 | ctx.beginPath(); 27 | ctx.lineTo(100, 0); 28 | ctx.lineTo(100, 200); 29 | ctx.stroke(); 30 | 31 | ctx.globalAlpha = 1; 32 | ctx.font = 'normal 40px Impact, serif'; 33 | 34 | ctx.rotate(0.5); 35 | ctx.translate(20, -40); 36 | 37 | ctx.lineWidth = 1; 38 | ctx.strokeStyle = '#ddd'; 39 | ctx.strokeText("Wahoo", 50, 100); 40 | 41 | ctx.fillStyle = '#000'; 42 | ctx.fillText("Wahoo", 49, 99); 43 | 44 | var m = ctx.measureText("Wahoo"); 45 | 46 | ctx.strokeStyle = '#f00'; 47 | 48 | ctx.strokeRect(49 + m.actualBoundingBoxLeft, 49 | 99 - m.actualBoundingBoxAscent, 50 | m.actualBoundingBoxRight - m.actualBoundingBoxLeft, 51 | m.actualBoundingBoxAscent + m.actualBoundingBoxDescent); 52 | 53 | canvas.vgSwapBuffers(); 54 | eu.handleTermination(); 55 | eu.waitForInput(); 56 | -------------------------------------------------------------------------------- /examples/textSpeed.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node-canvas 2 | /*jslint indent: 2, node: true */ 3 | "use strict"; 4 | 5 | var util = require('util'); 6 | var Canvas = require('../lib/canvas'); 7 | var canvas = new Canvas(800, 800); 8 | var ctx = canvas.getContext('2d'); 9 | 10 | var eu = require('./util'); 11 | 12 | ctx.clearRect(0, 0, canvas.width, canvas.height); 13 | ctx.font = 'normal 20px serif'; 14 | 15 | var i = 0; 16 | for (;;) { 17 | var r = Math.floor(Math.random() * 256); 18 | var g = Math.floor(Math.random() * 256); 19 | var b = Math.floor(Math.random() * 256); 20 | ctx.fillStyle = 'rgba(' + r + ', ' + g + ', ' + b + ', 0.5)'; 21 | 22 | var x = canvas.width * Math.random(); 23 | var y = canvas.height * Math.random(); 24 | ctx.fillText('Grumpy wizards make toxic brew for the evil Queen and Jack.', x, y); 25 | 26 | canvas.vgSwapBuffers(); 27 | 28 | i++; 29 | if (i % 1000 === 0) { 30 | console.log('Done ' + i + ' strings.'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/util.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var eu = module.exports; 5 | 6 | var fs = require('fs'); 7 | var image = require('../lib/image'); 8 | 9 | var animationHandle; 10 | 11 | var stopWatch = eu.stopWatch = function (str, fn) { 12 | var end, start; 13 | start = new Date(); 14 | fn(); 15 | end = new Date(); 16 | console.log(str + ' took: ' + (end - start) + 'ms'); 17 | }; 18 | 19 | var animate = eu.animate = function (paint) { 20 | (function animloop(time) { 21 | animationHandle = requestAnimationFrame(animloop); 22 | paint(time); 23 | })(); 24 | }; 25 | 26 | var handleTermination = eu.handleTermination = function (callback) { 27 | function terminate() { 28 | if (callback) { callback(); } 29 | console.log("Making a clean exit."); 30 | } 31 | process.on('exit', terminate); 32 | }; 33 | 34 | var waitForInput = eu.waitForInput = function (prompt, callback) { 35 | if (prompt === undefined) { 36 | prompt = 'Press return to exit.'; 37 | } else if (callback === undefined) { 38 | callback = prompt; 39 | prompt = 'Press return to exit.'; 40 | } 41 | 42 | console.log(prompt); 43 | process.stdin.resume(); 44 | process.stdin.setEncoding('utf8'); 45 | 46 | process.stdin.once('data', function (chunk) { 47 | cancelAnimationFrame(animationHandle); 48 | if (callback) { 49 | callback(); 50 | } else { 51 | process.stdin.pause(); 52 | } 53 | }); 54 | }; 55 | 56 | var saveScreenshot = eu.saveScreenshot = function (ctx, x, y, w, h, filename) { 57 | var imageData = ctx.getImageData(x, y, w, h); 58 | var buffer = image.saveToBuffer(imageData); 59 | fs.writeFile(filename, buffer); 60 | }; -------------------------------------------------------------------------------- /lib/canvas.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var vg = require('openvg'); 5 | var context = require('./context.js'); 6 | var image = require('./image.js'); 7 | var Path = require('./path.js'); 8 | var DrawingStyle = require('./drawingStyle'); 9 | var m = require('./matrix.js'); 10 | 11 | var notImplemented = function () { 12 | return 'Not Implemented'; 13 | }; 14 | 15 | var Canvas = module.exports = function (width, height) { 16 | var self = this; 17 | 18 | vg.init(); 19 | 20 | width = vg.screen.width; 21 | height = vg.screen.height; 22 | 23 | var context2d; 24 | 25 | function getWidth() { return width; } 26 | Object.defineProperty(this, 'width', { enumerable: true, get: getWidth }); 27 | 28 | function getHeight() { return height; } 29 | Object.defineProperty(this, 'height', { enumerable: true, get: getHeight }); 30 | 31 | context2d = context.createCanvasRenderingContext2D(this); 32 | 33 | this.toDataURL = notImplemented; 34 | this.toDataURLHD = notImplemented; 35 | this.toBlob = notImplemented; 36 | this.toBlobHD = notImplemented; 37 | 38 | this.getContext = function (contextId, args) { 39 | if (contextId === '2d') { 40 | return context2d; 41 | } else { 42 | return null; 43 | } 44 | }; 45 | 46 | // Conform to node-canvas API 47 | this.toBuffer = function () { 48 | return image.saveToBuffer(context2d.getImageData(0, 0, width, height)); 49 | }; 50 | }; 51 | 52 | // Conform to node-canvas API 53 | Canvas.Image = image.Image; 54 | 55 | Canvas.Path = Path; 56 | Canvas.SVGMatrix = m.SVGMatrix; 57 | Canvas.DrawingStyle = DrawingStyle; 58 | 59 | Canvas.vgSwapBuffers = function () { 60 | vg.egl.swapBuffers(vg.screen.surface); 61 | }; 62 | 63 | // Based on http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 64 | // No need for iife, module scope is already isolated 65 | if (!global.requestAnimationFrame) { 66 | var lastTime = 0; 67 | 68 | global.requestAnimationFrame = function (callback) { 69 | var currTime = Date.now(); 70 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 71 | var id = setTimeout(function () { 72 | callback(currTime + timeToCall); 73 | Canvas.vgSwapBuffers(); 74 | }, 75 | timeToCall); 76 | lastTime = currTime + timeToCall; 77 | return id; 78 | }; 79 | 80 | global.cancelAnimationFrame = function (id) { 81 | clearTimeout(id); 82 | }; 83 | } 84 | -------------------------------------------------------------------------------- /lib/color.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var color = module.exports; 5 | 6 | // This array was originaly found at 7 | // https://github.com/LearnBoost/node-canvas/blob/master/src/color.cc 8 | var namedColors = { 9 | transparent : 0x000000, 10 | aliceblue : 0xF0F8FF, 11 | antiquewhite : 0xFAEBD7, 12 | aqua : 0x00FFFF, 13 | aquamarine : 0x7FFFD4, 14 | azure : 0xF0FFFF, 15 | beige : 0xF5F5DC, 16 | bisque : 0xFFE4C4, 17 | black : 0x000000, 18 | blanchedalmond : 0xFFEBCD, 19 | blue : 0x0000FF, 20 | blueviolet : 0x8A2BE2, 21 | brown : 0xA52A2A, 22 | burlywood : 0xDEB887, 23 | cadetblue : 0x5F9EA0, 24 | chartreuse : 0x7FFF00, 25 | chocolate : 0xD2691E, 26 | coral : 0xFF7F50, 27 | cornflowerblue : 0x6495ED, 28 | cornsilk : 0xFFF8DC, 29 | crimson : 0xDC143C, 30 | cyan : 0x00FFFF, 31 | darkblue : 0x00008B, 32 | darkcyan : 0x008B8B, 33 | darkgoldenrod : 0xB8860B, 34 | darkgray : 0xA9A9A9, 35 | darkgreen : 0x006400, 36 | darkgrey : 0xA9A9A9, 37 | darkkhaki : 0xBDB76B, 38 | darkmagenta : 0x8B008B, 39 | darkolivegreen : 0x556B2F, 40 | darkorange : 0xFF8C00, 41 | darkorchid : 0x9932CC, 42 | darkred : 0x8B0000, 43 | darksalmon : 0xE9967A, 44 | darkseagreen : 0x8FBC8F, 45 | darkslateblue : 0x483D8B, 46 | darkslategray : 0x2F4F4F, 47 | darkslategrey : 0x2F4F4F, 48 | darkturquoise : 0x00CED1, 49 | darkviolet : 0x9400D3, 50 | deeppink : 0xFF1493, 51 | deepskyblue : 0x00BFFF, 52 | dimgray : 0x696969, 53 | dimgrey : 0x696969, 54 | dodgerblue : 0x1E90FF, 55 | firebrick : 0xB22222, 56 | floralwhite : 0xFFFAF0, 57 | forestgreen : 0x228B22, 58 | fuchsia : 0xFF00FF, 59 | gainsboro : 0xDCDCDC, 60 | ghostwhite : 0xF8F8FF, 61 | gold : 0xFFD700, 62 | goldenrod : 0xDAA520, 63 | gray : 0x808080, 64 | green : 0x008000, 65 | greenyellow : 0xADFF2F, 66 | grey : 0x808080, 67 | honeydew : 0xF0FFF0, 68 | hotpink : 0xFF69B4, 69 | indianred : 0xCD5C5C, 70 | indigo : 0x4B0082, 71 | ivory : 0xFFFFF0, 72 | khaki : 0xF0E68C, 73 | lavender : 0xE6E6FA, 74 | lavenderblush : 0xFFF0F5, 75 | lawngreen : 0x7CFC00, 76 | lemonchiffon : 0xFFFACD, 77 | lightblue : 0xADD8E6, 78 | lightcoral : 0xF08080, 79 | lightcyan : 0xE0FFFF, 80 | lightgoldenrodyellow : 0xFAFAD2, 81 | lightgray : 0xD3D3D3, 82 | lightgreen : 0x90EE90, 83 | lightgrey : 0xD3D3D3, 84 | lightpink : 0xFFB6C1, 85 | lightsalmon : 0xFFA07A, 86 | lightseagreen : 0x20B2AA, 87 | lightskyblue : 0x87CEFA, 88 | lightslategray : 0x778899, 89 | lightslategrey : 0x778899, 90 | lightsteelblue : 0xB0C4DE, 91 | lightyellow : 0xFFFFE0, 92 | lime : 0x00FF00, 93 | limegreen : 0x32CD32, 94 | linen : 0xFAF0E6, 95 | magenta : 0xFF00FF, 96 | maroon : 0x800000, 97 | mediumaquamarine : 0x66CDAA, 98 | mediumblue : 0x0000CD, 99 | mediumorchid : 0xBA55D3, 100 | mediumpurple : 0x9370DB, 101 | mediumseagreen : 0x3CB371, 102 | mediumslateblue : 0x7B68EE, 103 | mediumspringgreen : 0x00FA9A, 104 | mediumturquoise : 0x48D1CC, 105 | mediumvioletred : 0xC71585, 106 | midnightblue : 0x191970, 107 | mintcream : 0xF5FFFA, 108 | mistyrose : 0xFFE4E1, 109 | moccasin : 0xFFE4B5, 110 | navajowhite : 0xFFDEAD, 111 | navy : 0x000080, 112 | oldlace : 0xFDF5E6, 113 | olive : 0x808000, 114 | olivedrab : 0x6B8E23, 115 | orange : 0xFFA500, 116 | orangered : 0xFF4500, 117 | orchid : 0xDA70D6, 118 | palegoldenrod : 0xEEE8AA, 119 | palegreen : 0x98FB98, 120 | paleturquoise : 0xAFEEEE, 121 | palevioletred : 0xDB7093, 122 | papayawhip : 0xFFEFD5, 123 | peachpuff : 0xFFDAB9, 124 | peru : 0xCD853F, 125 | pink : 0xFFC0CB, 126 | plum : 0xDDA0DD, 127 | powderblue : 0xB0E0E6, 128 | purple : 0x800080, 129 | red : 0xFF0000, 130 | rosybrown : 0xBC8F8F, 131 | royalblue : 0x4169E1, 132 | saddlebrown : 0x8B4513, 133 | salmon : 0xFA8072, 134 | sandybrown : 0xF4A460, 135 | seagreen : 0x2E8B57, 136 | seashell : 0xFFF5EE, 137 | sienna : 0xA0522D, 138 | silver : 0xC0C0C0, 139 | skyblue : 0x87CEEB, 140 | slateblue : 0x6A5ACD, 141 | slategray : 0x708090, 142 | slategrey : 0x708090, 143 | snow : 0xFFFAFA, 144 | springgreen : 0x00FF7F, 145 | steelblue : 0x4682B4, 146 | tan : 0xD2B48C, 147 | teal : 0x008080, 148 | thistle : 0xD8BFD8, 149 | tomato : 0xFF6347, 150 | turquoise : 0x40E0D0, 151 | violet : 0xEE82EE, 152 | wheat : 0xF5DEB3, 153 | white : 0xFFFFFF, 154 | whitesmoke : 0xF5F5F5, 155 | yellow : 0xFFFF00, 156 | yellowgreen : 0x9ACD32 157 | }; 158 | 159 | var namedColorValues = new Float32Array(Object.keys(namedColors).length * 4); 160 | 161 | (function () { 162 | var pos = 0; 163 | for (var color in namedColors) { 164 | var rgb = namedColors[color]; 165 | namedColors[color] = pos; 166 | namedColorValues[pos++] = (rgb >>> 16) / 255; 167 | namedColorValues[pos++] = (rgb >>> 8 & 0xff) / 255; 168 | namedColorValues[pos++] = (rgb & 0xff) / 255; 169 | namedColorValues[pos++] = 1.0; 170 | } 171 | namedColorValues[namedColors['transparent'] + 3] = 0.0; 172 | })(); 173 | 174 | var applyAlpha = color.applyAlpha = function (dest, vector, alpha) { 175 | dest[0] = vector[0]; 176 | dest[1] = vector[1]; 177 | dest[2] = vector[2]; 178 | dest[3] = vector[3] * alpha; 179 | }; 180 | 181 | function parseHexColor(dest, colorString) { 182 | var hexLen = 1, r, g, b; 183 | while (hexLen < colorString.length) { 184 | var c = colorString.charCodeAt(hexLen); 185 | if (!(c >= 48 && c <= 48 + 9) && 186 | !(c >= 65 && c <= 65 + 5) && 187 | !(c >= 97 && c <= 97 + 5)) 188 | break; 189 | hexLen++; 190 | } 191 | 192 | if (hexLen === 7) { 193 | dest[0] = parseInt(colorString.substr(1, 2), 16) / 255.0; 194 | dest[1] = parseInt(colorString.substr(3, 2), 16) / 255.0; 195 | dest[2] = parseInt(colorString.substr(5, 2), 16) / 255.0; 196 | dest[3] = 1.0; 197 | } else if (hexLen === 4) { 198 | r = parseInt(colorString.substr(1, 1), 16); 199 | g = parseInt(colorString.substr(2, 1), 16); 200 | b = parseInt(colorString.substr(3, 1), 16); 201 | dest[0] = (r << 4 | r) / 255.0; 202 | dest[1] = (g << 4 | g) / 255.0; 203 | dest[2] = (b << 4 | b) / 255.0; 204 | dest[3] = 1.0; 205 | } 206 | } 207 | 208 | // TO DO: Handle percentage values (http://dev.w3.org/csswg/css3-color/#rgba-color) 209 | function parseRGBAColor(dest, colorString) { 210 | colorString = colorString.substr(5).split(/ *, */); 211 | 212 | if (colorString[0].charAt(colorString[0].length - 1) === '%') { 213 | dest[0] = parseFloat(colorString[0]) / 100; 214 | dest[1] = parseFloat(colorString[1]) / 100; 215 | dest[2] = parseFloat(colorString[2]) / 100; 216 | } else { 217 | dest[0] = parseInt(colorString[0], 10) / 255.0; 218 | dest[1] = parseInt(colorString[1], 10) / 255.0; 219 | dest[2] = parseInt(colorString[2], 10) / 255.0; 220 | } 221 | 222 | dest[3] = parseFloat(colorString[3]); 223 | } 224 | 225 | // TO DO: Handle percentage values (http://dev.w3.org/csswg/css3-color/#rgb-color) 226 | function parseRGBColor(dest, colorString) { 227 | colorString = colorString.substr(4).split(/ *, */); 228 | 229 | if (colorString[0].charAt(colorString[0].length - 1) === '%') { 230 | dest[0] = parseFloat(colorString[0]) / 100; 231 | dest[1] = parseFloat(colorString[1]) / 100; 232 | dest[2] = parseFloat(colorString[2]) / 100; 233 | } else { 234 | dest[0] = parseInt(colorString[0], 10) / 255.0; 235 | dest[1] = parseInt(colorString[1], 10) / 255.0; 236 | dest[2] = parseInt(colorString[2], 10) / 255.0; 237 | } 238 | 239 | dest[3] = 1.0; 240 | } 241 | 242 | // http://dev.w3.org/csswg/css3-color/#hsl-color 243 | function hsl2RGB(dest, h, s, l) { 244 | if (h < 0) { 245 | h = (((h % 360) + 360) % 360); 246 | } else if (h >= 360) { 247 | h = h % 360; 248 | } 249 | 250 | var c = (1 - (l > 0.5 ? 2 * l - 1 : 1 - 2 * l)) * s; 251 | var hh = h / 60; 252 | var x = c * (1 - Math.abs(hh % 2 - 1)); 253 | 254 | if (hh === undefined) { 255 | dest[0] = dest[1] = dest[2] = 0; 256 | } else if (hh < 1) { 257 | dest[0] = c; 258 | dest[1] = x; 259 | dest[2] = 0; 260 | } else if (hh < 2) { 261 | dest[0] = x; 262 | dest[1] = c; 263 | dest[2] = 0; 264 | } else if (hh < 3) { 265 | dest[0] = 0; 266 | dest[1] = c; 267 | dest[2] = x; 268 | } else if (hh < 4) { 269 | dest[0] = 0; 270 | dest[1] = x; 271 | dest[2] = c; 272 | } else if (hh < 5) { 273 | dest[0] = x; 274 | dest[1] = 0; 275 | dest[2] = c; 276 | } else { 277 | dest[0] = c; 278 | dest[1] = 0; 279 | dest[2] = x; 280 | } 281 | 282 | var m = l - c * 0.5; 283 | dest[0] += m; 284 | dest[1] += m; 285 | dest[2] += m; 286 | } 287 | 288 | function parseHSLAColor(dest, colorString) { 289 | colorString = colorString.substr(5).split(/ *, */); 290 | var h = parseFloat(colorString[0]); 291 | var s = parseFloat(colorString[1]) * 0.01; 292 | var l = parseFloat(colorString[2]) * 0.01; 293 | 294 | hsl2RGB(dest, h, s, l); 295 | 296 | dest[3] = parseFloat(colorString[3]); 297 | } 298 | 299 | function parseHSLColor(dest, colorString) { 300 | colorString = colorString.substr(4).split(/ *, */); 301 | var h = parseFloat(colorString[0]); 302 | var s = parseFloat(colorString[1]) * 0.01; 303 | var l = parseFloat(colorString[2]) * 0.01; 304 | 305 | hsl2RGB(dest, h, s, l); 306 | 307 | dest[3] = 1.0; 308 | } 309 | 310 | function namedColor(dest, colorString) { 311 | var pos = namedColors[colorString]; 312 | if (pos !== undefined) { 313 | dest[0] = namedColorValues[pos++]; 314 | dest[1] = namedColorValues[pos++]; 315 | dest[2] = namedColorValues[pos++]; 316 | dest[3] = namedColorValues[pos]; 317 | } else { 318 | dest[0] = dest[1] = dest[2] = dest[3] = NaN; 319 | } 320 | } 321 | 322 | var parseColor = color.parseColor = function (dest, colorString) { 323 | if (colorString.charAt(0) === '#') { 324 | return parseHexColor(dest, colorString); 325 | } else if (colorString.indexOf('rgba(') === 0) { 326 | return parseRGBAColor(dest, colorString); 327 | } else if (colorString.indexOf('rgb(') === 0) { 328 | return parseRGBColor(dest, colorString); 329 | } else if (colorString.indexOf('hsla(') === 0) { 330 | return parseHSLAColor(dest, colorString); 331 | } else if (colorString.indexOf('hsl(') === 0) { 332 | return parseHSLColor(dest, colorString); 333 | } else { 334 | return namedColor(dest, colorString); 335 | } 336 | }; 337 | 338 | var floatToHex = color.floatToHex = function (v) { 339 | v = Math.floor(v * 255); 340 | if (v < 16) { 341 | return '0' + v.toString(16); 342 | } else { 343 | return v.toString(16); 344 | } 345 | }; 346 | 347 | var toHexColor = color.toHexColor = function (colorArray) { 348 | return '#' + floatToHex(colorArray[0]) + floatToHex(colorArray[1]) + 349 | floatToHex(colorArray[2]); 350 | }; 351 | 352 | var toRGBAColor = color.toRGBAColor = function (colorArray) { 353 | return 'rgba(' + colorArray[0] * 255 + colorArray[1] * 255 + 354 | colorArray[2] * 255 + ')'; 355 | }; 356 | 357 | var serialize = color.serialize = function (colorArray) { 358 | if (colorArray[3] === 1.0) { 359 | return toHexColor(colorArray); 360 | } else { 361 | return toRGBAColor(colorArray); 362 | } 363 | }; 364 | 365 | var logVColor = color.logVColor = function (label, c) { 366 | if (!c) { 367 | c = label; 368 | label = 'Color'; 369 | } 370 | console.log(label + ': [' + c[0] + ', ' + c[1] + ', ' + c[2] + ', ' + c[3] + ']'); 371 | }; 372 | -------------------------------------------------------------------------------- /lib/drawingStyle.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | /*global Float32Array: true */ 3 | "use strict"; 4 | 5 | var text = require('./text/text'); 6 | 7 | // interface CanvasDrawingStyles / DrawingStyle 8 | var DrawingStyle = module.exports = function () { 9 | // line caps/joins 10 | this.lineWidth = 1; 11 | this.lineCap = 'butt'; 12 | this.lineJoin = 'miter'; 13 | this.miterLimit = 10; 14 | 15 | // dashed lines 16 | this.lineDashSegments = new Float32Array(100); // TODO: 100 ? why 100 ? 17 | this.lineDashOffset = 0.0; 18 | 19 | // text 20 | this.font_ = undefined; 21 | this.fontInfo_ = undefined; 22 | this.textAlign = 'start'; 23 | this.textBaseline = 'alphabetic'; 24 | this.direction = 'ltr'; // Should be 'inherit', but we are not on an HTML page. 25 | 26 | this.font = '10px sans-serif'; 27 | }; 28 | 29 | DrawingStyle.prototype.setLineDash = function (segments) { 30 | this.lineDashSegments.set(segments, 0); 31 | }; 32 | 33 | DrawingStyle.prototype.getLineDash = function () { 34 | return this.lineDashSegments; 35 | }; 36 | 37 | function getFont() { 38 | return this.font_; 39 | } 40 | 41 | function setFont(font) { 42 | var fontInfo = text.parseFont(font); 43 | if (fontInfo) { 44 | this.font_ = text.serialize(fontInfo); 45 | this.fontInfo_ = fontInfo; 46 | } 47 | } 48 | 49 | Object.defineProperty(DrawingStyle.prototype, 'font', { get: getFont, set: setFont }); 50 | -------------------------------------------------------------------------------- /lib/gradient.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var vg = require('openvg'); 5 | var color = require('./color'); 6 | 7 | /** 8 | * Allocating Typed Arrays is extremely expensive, so, taking 9 | * single-threadedness into an advantage, all necessary typed arrays are 10 | * eagerly allocated, and shared by all gradients. 11 | * 12 | * All this busy work to avoid going to C land. 13 | */ 14 | 15 | var MAX_STOPS_ESTIMATE = 10; 16 | var EXCESS_FACTOR = 2; 17 | 18 | var colorBuffer = new Float32Array(4); 19 | var defaultStopArray = new Float32Array([0, 0, 0, 0, 0, 1, 1, 1, 1, 1]); 20 | var parameterArray = new Float32Array(5); 21 | var stopArray = new Float32Array(MAX_STOPS_ESTIMATE * 5); 22 | 23 | function ensureStopArrayCapacity(stopCount) { 24 | if (stopArray.length < 5 * stopCount) { 25 | stopArray = new Float32Array(stopCount * 5 * EXCESS_FACTOR); 26 | } 27 | } 28 | 29 | var Gradient = module.exports = function (type, parameters) { 30 | this.type = type; 31 | this.parameters = parameters; 32 | this.stopCount = 0; 33 | this.stops = null; 34 | }; 35 | 36 | Gradient.prototype.addColorStop = function (stop, baseColor) { 37 | color.parseColor(colorBuffer, baseColor); 38 | this.stops = { 39 | next : this.stops, 40 | stop : stop, 41 | r : colorBuffer[0], 42 | g : colorBuffer[1], 43 | b : colorBuffer[2], 44 | a : colorBuffer[3] 45 | }; 46 | 47 | this.stopCount++; 48 | }; 49 | 50 | Gradient.prototype.stopArray = function (alpha) { 51 | if (this.stopCount === 0) { 52 | defaultStopArray[9] = alpha; 53 | return defaultStopArray; 54 | } 55 | 56 | ensureStopArrayCapacity(this.stopCount); 57 | 58 | var i = this.stopCount * 5; 59 | var stop = this.stops; 60 | do { 61 | stopArray[--i] = stop.a * alpha; 62 | stopArray[--i] = stop.b; 63 | stopArray[--i] = stop.g; 64 | stopArray[--i] = stop.r; 65 | stopArray[--i] = stop.stop; 66 | stop = stop.next; 67 | } while (i > 0); 68 | 69 | return stopArray; 70 | }; 71 | 72 | Gradient.prototype.parameterArray = function () { 73 | for (var i = 0; i < this.parameters.length; i++) { 74 | parameterArray[i] = this.parameters[i]; 75 | } 76 | return parameterArray; 77 | }; 78 | 79 | Gradient.prototype.configurePaint = function (paint, alpha) { 80 | var paintType, paintParam; 81 | 82 | if ('linearGradient' === this.type) { 83 | paintType = vg.VGPaintType.VG_PAINT_TYPE_LINEAR_GRADIENT; 84 | paintParam = vg.VGPaintParamType.VG_PAINT_LINEAR_GRADIENT; 85 | } else { 86 | paintType = vg.VGPaintType.VG_PAINT_TYPE_RADIAL_GRADIENT; 87 | paintParam = vg.VGPaintParamType.VG_PAINT_RADIAL_GRADIENT; 88 | } 89 | 90 | vg.setParameterI(paint, vg.VGPaintParamType.VG_PAINT_TYPE, paintType); 91 | vg.setParameterFVOL(paint, paintParam, this.parameterArray(), 0, this.parameters.length); 92 | 93 | vg.setParameterI(paint, 94 | vg.VGPaintParamType.VG_PAINT_COLOR_RAMP_SPREAD_MODE, 95 | vg.VGColorRampSpreadMode.VG_COLOR_RAMP_SPREAD_PAD); 96 | vg.setParameterI(paint, 97 | vg.VGPaintParamType.VG_PAINT_COLOR_RAMP_PREMULTIPLIED, 98 | 0 /* VG_FALSE */); 99 | vg.setParameterFVOL(paint, 100 | vg.VGPaintParamType.VG_PAINT_COLOR_RAMP_STOPS, 101 | this.stopArray(alpha), 0, this.stopCount * 5); 102 | }; 103 | -------------------------------------------------------------------------------- /lib/image.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | /*global Image: true */ 3 | "use strict"; 4 | 5 | var image = module.exports; 6 | 7 | var FreeImage = require(__dirname + "/../build/Release/freeimage").FreeImage; 8 | 9 | var util = require('util'); 10 | var vg = require('openvg'); 11 | var m = require('./matrix'); 12 | 13 | var scissorRects = new Uint32Array(4); 14 | 15 | function loadImage(freeImage) { 16 | var result = { 17 | width: null, 18 | height: null, 19 | vgHandle: null 20 | }; 21 | 22 | if (freeImage.bpp !== 32) { 23 | freeImage = freeImage.convertTo32Bits(); 24 | } 25 | var width = result.width = freeImage.width; 26 | var height = result.height = freeImage.height; 27 | 28 | result.vgHandle = 29 | vg.createImage(vg.VGImageFormat.VG_sARGB_8888, width, height, 30 | vg.VGImageQuality.VG_IMAGE_QUALITY_BETTER); 31 | 32 | // Flip the image 33 | var buffer = freeImage.buffer.slice((height - 1) * (width * 4), 34 | height * (width * 4)); 35 | vg.imageSubData(result.vgHandle, buffer, - width * 4, 36 | vg.VGImageFormat.VG_sARGB_8888, 37 | 0, 0, width, height); // sx, sy, w, h 38 | 39 | return result; 40 | } 41 | 42 | function loadImageFromBuffer(buffer) { 43 | return loadImage(FreeImage.loadFromMemory(buffer)); 44 | } 45 | 46 | var Image = image.Image = function () { 47 | var self = this; 48 | this.width_ = 0; 49 | this.height_ = 0; 50 | this.vgHandle_ = undefined; 51 | this.complete_ = false; 52 | this.destroyed_ = false; 53 | 54 | function setSource(data) { 55 | var imgData = loadImageFromBuffer(data); 56 | self.width_ = imgData.width; 57 | self.height_ = imgData.height; 58 | self.vgHandle_ = imgData.vgHandle; 59 | self.complete_ = true; 60 | } 61 | Object.defineProperty(this, 'src', { enumerable: false, set: setSource }); 62 | 63 | function getWidth() { return self.width_; } 64 | Object.defineProperty(this, 'width', { enumerable: true, get: getWidth }); 65 | 66 | function getHeight() { return self.height_; } 67 | Object.defineProperty(this, 'height', { enumerable: true, get: getHeight }); 68 | 69 | function getHandle() { return self.vgHandle_; } 70 | Object.defineProperty(this, 'vgHandle', { enumerable: false, get: getHandle }); 71 | 72 | function getComplete() { return self.complete_; } 73 | Object.defineProperty(this, 'complete', { enumerable: false, get: getComplete }); 74 | 75 | // For now, images must be destroyed manually 76 | function getVgDestroyed() { return self.destroyed_; } 77 | Object.defineProperty(this, 'vgDestroyed', { enumerable: true, get: getVgDestroyed }); 78 | 79 | this.vgDestroy = function () { 80 | self.destroyed_ = true; 81 | if (self.complete_) { 82 | vg.destroyImage(self.vgHandle_); 83 | } 84 | }; 85 | }; 86 | 87 | var drawImage = image.drawImage = 88 | function (img, sx, sy, sw, sh, dx, dy, dw, dh, paintFn) { 89 | 90 | if (!img.complete) return; // Don't even bother 91 | 92 | var mm = m.create(); 93 | 94 | vg.getMatrix(mm); 95 | 96 | var savMatrixMode = vg.getI(vg.VGParamType.VG_MATRIX_MODE); 97 | vg.setI(vg.VGParamType.VG_MATRIX_MODE, 98 | vg.VGMatrixMode.VG_MATRIX_IMAGE_USER_TO_SURFACE); 99 | 100 | vg.loadMatrix(mm); 101 | vg.translate(dx, dy); 102 | vg.scale(dw / sw, dh / sh); 103 | 104 | vg.setI(vg.VGParamType.VG_IMAGE_MODE, 105 | vg.VGImageMode.VG_DRAW_IMAGE_NORMAL); 106 | 107 | if (sx === 0 && sy === 0 && sw === img.width && sh === img.height) { 108 | paintFn(img.vgHandle); 109 | } else { 110 | var vgSubHandle = 111 | vg.createImage(vg.VGImageFormat.VG_sARGB_8888, sw, sh, 112 | vg.VGImageQuality.VG_IMAGE_QUALITY_BETTER); 113 | 114 | vg.copyImage(vgSubHandle, 0, 0, img.vgHandle, sx, sy, sw, sh, true /* dither */); 115 | paintFn(vgSubHandle); 116 | vg.destroyImage(vgSubHandle); 117 | } 118 | 119 | vg.setI(vg.VGParamType.VG_MATRIX_MODE, savMatrixMode); 120 | vg.loadMatrix(mm); 121 | }; 122 | 123 | var ImageData = image.ImageData = function (w, h, d) { 124 | var width = w, height = h, data = d; 125 | 126 | function getWidth() { 127 | return width; 128 | } 129 | Object.defineProperty(this, 'width', { enumerable: true, get: getWidth }); 130 | 131 | function getHeight() { 132 | return height; 133 | } 134 | Object.defineProperty(this, 'height', { enumerable: true, get: getHeight }); 135 | 136 | function getData() { 137 | if (data === undefined) { 138 | data = new Uint32Array(w * h); 139 | for (var i = 0; i < h * w; i++) { data[i] = 0x00; } 140 | data = new Uint8Array(data); 141 | } 142 | 143 | return data; 144 | } 145 | Object.defineProperty(this, 'data', { enumerable: false, get: getData }); 146 | }; 147 | 148 | var createImageDataWH = image.createImageDataWH = function (w, h) { 149 | return new ImageData(w, h); 150 | }; 151 | 152 | var createImageDataImg = image.createImageDataImg = function (img) { 153 | return createImageDataImg(img.width, img.height); 154 | }; 155 | 156 | // Image format is RBGA, as specified in: 157 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#dom-imagedata-data 158 | var getImageData = image.getImageData = function (sx, sy, sw, sh) { 159 | sy = vg.screen.height - sy - sh; 160 | var data = new Uint8Array(sw * sh * 4); 161 | 162 | vg.readPixels(data, sw * 4, 163 | vg.VGImageFormat.VG_sRGBA_8888, 164 | sx, sy, sw, sh); 165 | 166 | var result = new ImageData(sw, sh, data); 167 | 168 | return result; 169 | }; 170 | 171 | function writePixels(imagedata, dx, dy) { 172 | vg.writePixels(imagedata.data, imagedata.width * 4, 173 | vg.VGImageFormat.VG_sRGBA_8888, 174 | dx, dy, imagedata.width, imagedata.height); 175 | } 176 | 177 | function withScissoring(dirtyX, dirtyY, dirtyWidth, dirtyHeight, callback) { 178 | var saveScissoring = vg.getI(vg.VGParamType.VG_SCISSORING); 179 | vg.setI(vg.VGParamType.VG_SCISSORING, 1 /* true */); 180 | 181 | scissorRects[0] = dirtyX; 182 | scissorRects[1] = dirtyY; 183 | scissorRects[2] = dirtyWidth; 184 | scissorRects[3] = dirtyHeight; 185 | 186 | vg.setIV(vg.VGParamType.VG_SCISSOR_RECTS, scissorRects); 187 | 188 | callback(); 189 | 190 | vg.setI(vg.VGParamType.VG_SCISSORING, saveScissoring); 191 | } 192 | 193 | var putImageData = image.putImageData = function (imagedata, dx, dy, 194 | dirtyX, dirtyY, dirtyWidth, dirtyHeight) { 195 | var data = imagedata.data; 196 | 197 | dy = vg.screen.height - dy - imagedata.height; 198 | 199 | if (dirtyX) { 200 | dirtyY = vg.screen.height - dirtyY - dirtyHeight; 201 | withScissoring(dirtyX, dirtyY, dirtyWidth, dirtyHeight, function () { 202 | writePixels(imagedata, dx, dy); 203 | }); 204 | } else { 205 | writePixels(imagedata, dx, dy); 206 | } 207 | }; 208 | 209 | var saveToBuffer = image.saveToBuffer = function (imagedata) { 210 | var freeImage = 211 | FreeImage.convertFromRGBABits(imagedata.data, imagedata.width, imagedata.height); 212 | 213 | return freeImage.saveToMemory(); 214 | }; 215 | 216 | function logBuffer(label, buffer) { 217 | if (buffer === undefined) { 218 | buffer = label; 219 | label = 'buffer'; 220 | } 221 | 222 | if (buffer === undefined) { 223 | console.log(label + ': '); 224 | return; 225 | } 226 | 227 | if (buffer === null) { 228 | console.log(label + ': '); 229 | return; 230 | } 231 | 232 | console.log(label + ': ' + buffer[0].toString(16) + ', ' + buffer[1].toString(16) + ', ' + 233 | buffer[2].toString(16) + ', ' + buffer[3].toString(16)); 234 | } 235 | -------------------------------------------------------------------------------- /lib/matrix.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var matrix = module.exports; 5 | 6 | var create = matrix.create = function () { 7 | return new Float32Array(9); 8 | }; 9 | 10 | var m = matrix.m = function (a, b, c, d, e, f, g, h, k) { 11 | var r = new Float32Array(9); 12 | r[0] = a; r[1] = b; r[2] = c; 13 | r[3] = d; r[4] = e; r[5] = f; 14 | r[6] = g; r[7] = h; r[8] = k; 15 | return r; 16 | }; 17 | 18 | var logMatrix = matrix.log = function (label, m) { 19 | if (!m) { 20 | m = label; 21 | label = 'M'; 22 | } 23 | 24 | var maxLen = 0; 25 | var nums = new Array(9); 26 | for (var i = 0; i < 9; i++) { 27 | nums[i] = m[i].toString(); 28 | if (nums[i].length > maxLen) { 29 | maxLen = nums[i].length; 30 | } 31 | } 32 | 33 | var padSpaces = ' '; 34 | while (padSpaces.length < label.length) { padSpaces += padSpaces; } 35 | 36 | function pad(text) { 37 | return padSpaces.substr(0, maxLen - text.length) + text; 38 | } 39 | 40 | function padL(text) { 41 | return padSpaces.substr(0, text.length); 42 | } 43 | 44 | console.log( label + ': ⎡' + pad(nums[0]) + ' , ' + pad(nums[1]) + ' , ' + pad(nums[2]) + '⎤'); 45 | console.log(padL(label) + ' ⎢' + pad(nums[3]) + ' , ' + pad(nums[4]) + ' , ' + pad(nums[5]) + '⎥'); 46 | console.log(padL(label) + ' ⎣' + pad(nums[6]) + ' , ' + pad(nums[7]) + ' , ' + pad(nums[8]) + '⎦'); 47 | 48 | return m; 49 | }; 50 | 51 | var SVGMatrix = matrix.SVGMatrix = function (m) { 52 | this.m = m || matrix.m(1, 0, 0, 0, 1, 0, 0, 0, 1); 53 | }; 54 | 55 | SVGMatrix.prototype.getComponent = function (index) { 56 | var tMap = [0, 1, 3, 4, 6, 7]; 57 | // TO DO: Raise exception if index < 0 || index > 5 58 | return this.m[tMap[index]]; 59 | }; 60 | 61 | SVGMatrix.prototype.mMultiplyF = function (o0, o1, o2, o3, o4, o5, o6, o7, o8) { 62 | var m = this.m, m0, m1, m2, m3, m4, m5, m6, m7, m8; 63 | 64 | m0 = m[0] * o0 + m[1] * o3 + m[2] * o6; 65 | m1 = m[0] * o1 + m[1] * o4 + m[2] * o7; 66 | m2 = m[0] * o2 + m[1] * o5 + m[2] * o8; 67 | 68 | m3 = m[3] * o0 + m[4] * o3 + m[5] * o6; 69 | m4 = m[3] * o1 + m[4] * o4 + m[5] * o7; 70 | m5 = m[3] * o2 + m[4] * o5 + m[5] * o8; 71 | 72 | m6 = m[6] * o0 + m[7] * o3 + m[8] * o6; 73 | m7 = m[6] * o1 + m[7] * o4 + m[8] * o7; 74 | m8 = m[6] * o2 + m[7] * o5 + m[8] * o8; 75 | 76 | m[0] = m0; m[1] = m1; m[2] = m2; 77 | m[3] = m3; m[4] = m4; m[5] = m5; 78 | m[6] = m6; m[7] = m7; m[8] = m8; 79 | 80 | return this; 81 | }; 82 | 83 | SVGMatrix.prototype.mMultiply = function (secondMatrix) { 84 | var m = secondMatrix.m, a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5], g = m[6], h = m[7], k = m[8]; 85 | return this.mMultiply(a, b, c, d, e, f, g, h, k); 86 | }; 87 | 88 | SVGMatrix.prototype.inverse = function () { 89 | var m = this.m, a = m[0], b = m[1], c = m[2], d = m[3], e = m[4], f = m[5], g = m[6], h = m[7], k = m[8]; 90 | var det = a * (e * k - f * h) - b * (d * k - f * g) + c * (d * h - e * g); 91 | // TO DO: Raise exception if det === 0 92 | return new SVGMatrix(matrix.m( 93 | a / det, d / det, g / det, 94 | b / det, e / det, h / det, 95 | c / det, f / det, k / det 96 | )); 97 | }; 98 | 99 | SVGMatrix.prototype.mTranslate = function (x, y) { 100 | return this.mMultiplyF(1, 0, 0, 0, 1, 0, x, y, 1); 101 | }; 102 | 103 | SVGMatrix.prototype.mScale = function (scaleFactor) { 104 | return this.mMultiplyF(scaleFactor, 0, 0, 0, scaleFactor, 0, 0, 0, 1); 105 | }; 106 | 107 | SVGMatrix.prototype.vgScale = function (scaleFactorX, scaleFactorY) { 108 | return this.mMultiplyF(scaleFactorX, 0, 0, 0, scaleFactorY, 0, 0, 0, 1); 109 | }; 110 | 111 | SVGMatrix.prototype.mRotate = function (angle) { 112 | var sin = Math.sin(angle); 113 | var cos = Math.cos(angle); 114 | 115 | return this.mMultiplyF(cos, -sin, 0, sin, cos, 0, 0, 0, 1); 116 | }; 117 | -------------------------------------------------------------------------------- /lib/pattern.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var vg = require('openvg'); 5 | var m = require('./matrix'); 6 | 7 | var Pattern = module.exports = function (image, repetition) { 8 | repetition = repetition === 'no-repeat' ? 'no-repeat' : 'repeat'; 9 | 10 | this.type = 'pattern'; 11 | this.image = image; 12 | this.tilingMode = repetition === 'repeat' ? 13 | vg.VGTilingMode.VG_TILE_REPEAT : 14 | vg.VGTilingMode.VG_TILE_FILL; 15 | this.repetition = repetition; 16 | this.transform = new m.SVGMatrix(); 17 | }; 18 | 19 | Pattern.prototype.setTransform = function (transform) { 20 | this.transform = transform; 21 | }; 22 | -------------------------------------------------------------------------------- /lib/text/freetype.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var freetype = module.exports = require('../../build/Release/freetype.node'); 5 | 6 | freetype.LOAD_DEFAULT = 0x0; 7 | freetype.LOAD_NO_SCALE = 1 << 0; 8 | freetype.LOAD_NO_HINTING = 1 << 1; 9 | freetype.LOAD_RENDER = 1 << 2; 10 | freetype.LOAD_NO_BITMAP = 1 << 3; 11 | freetype.LOAD_VERTICAL_LAYOUT = 1 << 4; 12 | freetype.LOAD_FORCE_AUTOHINT = 1 << 5; 13 | freetype.LOAD_CROP_BITMAP = 1 << 6; 14 | freetype.LOAD_PEDANTIC = 1 << 7; 15 | freetype.LOAD_IGNORE_GLOBAL_ADVANCE_WIDTH = 1 << 9; 16 | freetype.LOAD_NO_RECURSE = 1 << 10; 17 | freetype.LOAD_IGNORE_TRANSFORM = 1 << 11; 18 | freetype.LOAD_MONOCHROME = 1 << 12; 19 | freetype.LOAD_LINEAR_DESIGN = 1 << 13; 20 | freetype.LOAD_NO_AUTOHINT = 1 << 15; 21 | 22 | /* used internally only by certain font drivers! */ 23 | freetype.LOAD_ADVANCE_ONLY = 1 << 8; 24 | freetype.LOAD_SBITS_ONLY = 1 << 14; 25 | 26 | var library = freetype.library = freetype.initFreeType(); 27 | 28 | function done() { 29 | freetype.doneFreeType(library); 30 | } 31 | process.on('exit', done); 32 | -------------------------------------------------------------------------------- /lib/text/loading.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var domain = require('domain'); 5 | 6 | var fs = require('fs'); 7 | var util = require('util'); 8 | 9 | var vg = require('openvg'); 10 | var ft = require('./freetype'); 11 | 12 | var loading = module.exports; 13 | 14 | var FREETYPE_FONT_SIZE = 4096; 15 | var MAX_FONT_PATH = 255; 16 | var MAX_PRELOADED_CHARACTER = 255; 17 | 18 | var fontDomain = domain.create(); 19 | fontDomain.on('error', function (err) { 20 | console.error('Error on freetype font loading:\n' + 21 | err.message + '\n' + 22 | err.stack); 23 | }); 24 | 25 | var loadFontFile = loading.loadFontFile = function (name, callback) { 26 | fs.readFile(name, 'binary', fontDomain.bind(function (err, data) { 27 | if (err) { 28 | callback(err); 29 | } else { 30 | loadFont(new Buffer(data, 'binary'), callback); 31 | } 32 | })); 33 | }; 34 | 35 | var loadFontFileSync = loading.loadFontFileSync = function (name, callback) { 36 | var data = fs.readFileSync(name, 'binary'); 37 | loadFont(new Buffer(data, 'binary'), callback); 38 | }; 39 | 40 | function add(a, b) { 41 | return { x: a.x + b.x, y: a.y + b.y }; 42 | } 43 | function mult(a, b) { 44 | return { x: a.x * b, y: a.y * b }; 45 | } 46 | function scaleIn(x) { 47 | return x / FREETYPE_FONT_SIZE; 48 | } 49 | 50 | function scaleInVector(v) { 51 | return { x: scaleIn(v.x), y: scaleIn(v.y) }; 52 | } 53 | 54 | function toVector(array, index) { 55 | return { x: array[index * 2], y: array[index * 2 + 1] }; 56 | } 57 | 58 | function scaleOut(v) { 59 | return Math.floor(65536.0 * v); 60 | } 61 | 62 | function pushPoint(array, point) { 63 | array.push(scaleOut(point.x)); 64 | array.push(scaleOut(point.y)); 65 | } 66 | 67 | function isOn(b) { 68 | return b & 1; 69 | } 70 | 71 | function dumpArray(a, limit) { 72 | if (a === undefined) return "undefined"; 73 | if (a === null) return "null"; 74 | if (a.length === 0) return "[]"; 75 | 76 | if (limit === undefined) limit = a.length; 77 | 78 | var result = "[ " + a[0]; 79 | for (var i = 1; i < limit; i++) { 80 | result += ", " + a[i]; 81 | } 82 | return result + "]"; 83 | } 84 | 85 | // derived from http://web.archive.org/web/20070808195154/http://developer.hybrid.fi/font2openvg/font2openvg.cpp.txt 86 | var loadFont = loading.loadFont = function (fontBuffer, callback) { 87 | var f = { 88 | glyphs: new Uint32Array(MAX_FONT_PATH), 89 | characterMap: null, 90 | glyphAdvances: [], 91 | glyphBBoxes: [], 92 | face: null, // freetype typeface 93 | // font: null, // openvg typeface 94 | glyphCount: -1, 95 | ascender: null, 96 | descender: null 97 | }; 98 | 99 | var faceIndex = 0; 100 | 101 | // var font = f.font = vg.createFont(MAX_PRELOADED_CHARACTER + 1); 102 | var face = f.face = ft.newMemoryFace(ft.library, fontBuffer, faceIndex); 103 | 104 | // face, width, height, h_dpi, v_dpi 105 | ft.setCharSize(face, 0, FREETYPE_FONT_SIZE, 96, 96); 106 | 107 | var gpvecindices = []; 108 | var givecindices = []; 109 | var gpvecsizes = []; 110 | var givecsizes = []; 111 | var gpvec = []; 112 | var givec = []; 113 | var gbbox = f.glyphBBoxes; 114 | var i; 115 | 116 | f.characterMap = new Array(MAX_PRELOADED_CHARACTER + 1); 117 | var glyphs = 0; 118 | for (var cc = 0; cc <= MAX_PRELOADED_CHARACTER; cc++) { 119 | f.characterMap[cc] = -1; // initially nonexistent 120 | 121 | if (cc < 32) continue; // discard the first 32 characters 122 | 123 | var glyphIndex = ft.getCharIndex(face, cc); 124 | 125 | ft.loadGlyph(face, 126 | glyphIndex, 127 | ft.LOAD_NO_BITMAP | ft.LOAD_NO_HINTING | ft.LOAD_IGNORE_TRANSFORM); 128 | 129 | var path = vg.createPath(vg.VG_PATH_FORMAT_STANDARD, 130 | vg.VGPathDatatype.VG_PATH_DATATYPE_S_32, 131 | 1.0 / 65536.0 /* scale */, 132 | 0.0 /* bias */, 133 | 0, 0, /* segment, capacity hints */ 134 | vg.VGPathCapabilities.VG_PATH_CAPABILITY_ALL); 135 | f.glyphs[glyphs] = path; 136 | f.characterMap[cc] = glyphs++; 137 | 138 | f.glyphAdvances.push(scaleOut(scaleIn(face.glyph.advance.x))); 139 | 140 | var outline = face.glyph.outline; 141 | if (cc == 32) { 142 | gpvecindices.push(gpvec.length); 143 | givecindices.push(givec.length); 144 | 145 | gpvecsizes.push(0); 146 | givecsizes.push(0); 147 | 148 | gbbox.push({ minX: 0, minY: 0, maxX: 0, maxY: 0 }); 149 | 150 | continue; 151 | } 152 | 153 | var pvec = []; 154 | var ivec = []; 155 | var minX = 10000000.0, minY = 100000000.0, maxX = -10000000.0, maxY = -10000000.0; 156 | var s = 0, e; 157 | var on; 158 | var last, v, nv; 159 | for (var con = 0; con < outline.nContours; ++con) { 160 | var pnts = 1; 161 | e = outline.contours[con] + 1; 162 | last = scaleInVector(toVector(outline.points, s)); 163 | 164 | // read the contour start point 165 | ivec.push(vg.VGPathCommand.VG_MOVE_TO_ABS); 166 | pushPoint(pvec, last); 167 | 168 | i = s + 1; 169 | while (i <= e) { 170 | var c = (i == e) ? s : i; 171 | var n = (i == e - 1) ? s : (i + 1); 172 | v = scaleInVector(toVector(outline.points, c)); 173 | on = isOn(outline.tags[c]); 174 | if (on) { // line 175 | ++i; 176 | ivec.push(vg.VGPathCommand.VG_LINE_TO_ABS); 177 | pushPoint(pvec, v); 178 | pnts += 1; 179 | } else { // spline 180 | if (isOn(outline.tags[n])) { // next on 181 | nv = scaleInVector(toVector(outline.points, n)); 182 | i += 2; 183 | } else { // next off, use middle point 184 | nv = mult(add(v, scaleInVector(toVector(outline.points, n))), 0.5); 185 | ++i; 186 | } 187 | ivec.push(vg.VGPathCommand.VG_QUAD_TO_ABS); 188 | pushPoint(pvec, v); 189 | pushPoint(pvec, nv); 190 | pnts += 2; 191 | } 192 | last = nv; 193 | } 194 | ivec.push(vg.VGPathSegment.VG_CLOSE_PATH); 195 | s = e; 196 | } 197 | 198 | for (i = 0; i < pvec.length / 2; ++i) { 199 | if (pvec[i * 2 ] < minX) minX = pvec[i * 2 ]; 200 | if (pvec[i * 2 ] > maxX) maxX = pvec[i * 2 ]; 201 | if (pvec[i * 2 + 1] < minY) minY = pvec[i * 2 + 1]; 202 | if (pvec[i * 2 + 1] > maxY) maxY = pvec[i * 2 + 1]; 203 | } 204 | if (!pvec.length) { 205 | minX = 0.0; 206 | minY = 0.0; 207 | maxX = 0.0; 208 | maxY = 0.0; 209 | } 210 | 211 | gpvecindices.push(gpvec.length); 212 | givecindices.push(givec.length); 213 | 214 | gpvecsizes.push(pvec.length); 215 | givecsizes.push(ivec.length); 216 | 217 | gbbox.push({ minX: minX, minY: minY, maxX: maxX, maxY: maxY }); 218 | 219 | gpvec = gpvec.concat(pvec); 220 | givec = givec.concat(ivec); 221 | } 222 | 223 | f.glyphCount = glyphs; 224 | 225 | f.ascender = scaleOut(face.ascender / face.units_per_EM); 226 | f.descender = -scaleOut(face.descender / face.units_per_EM); 227 | 228 | var glyphPoints = new Int32Array(gpvec); 229 | var glyphInstructions = new Uint8Array(givec); 230 | 231 | for (i = 0; i < glyphs; i++) { 232 | var instructionCounts = givecsizes[i]; 233 | var glyphPath = f.glyphs[i]; 234 | 235 | if (instructionCounts) { 236 | var offset = gpvecindices[i]; 237 | 238 | vg.appendPathDataO(glyphPath, instructionCounts, 239 | glyphInstructions, givecindices[i], 240 | glyphPoints, gpvecindices[i] * 4); 241 | } 242 | 243 | // vg.setGlyphToPath(font, i, glyphPath, false /* VG_FALSE*/ /* isHinted*/, 244 | // new Float32Array([0,0]) /* glyphOrigin */, 245 | // new Float32Array([f.glyphAdvances[i],0]) /* escapement */); 246 | } 247 | 248 | if (callback) { 249 | callback(undefined, f); 250 | } 251 | }; 252 | 253 | // unloadfont frees font path data 254 | var unloadFont = loading.unloadFont = function (f) { 255 | // vg.destroyFont(f.font); 256 | ft.doneFace(f.face); 257 | for (var i = 0; i < f.glyphCount; i++) { 258 | vg.destroyPath(f.glyphs[i]); 259 | // vg.destroyGlyph(f.font, i); 260 | } 261 | }; 262 | -------------------------------------------------------------------------------- /lib/text/rendering.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var rendering = module.exports; 5 | 6 | var vg = require('openvg'); 7 | var m = require('../matrix'); 8 | var textFns = require('./text'); 9 | 10 | var MINIMUM_WIDTH_SCALE = 1.0 - 0.15; // Don't scale horizontally less than 15% 11 | 12 | var noop = function () { return 0; }; 13 | 14 | var textAlignOffsetFunctions = { 15 | left : noop, 16 | right : function (width) { return -width; }, 17 | center: function (width) { return -width / 2; }, 18 | start : null, 19 | end : null 20 | }; 21 | // Not in HTML context, so, _font style source node_ doesn't apply. 22 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#font-style-source-node 23 | textAlignOffsetFunctions.start = textAlignOffsetFunctions.left; 24 | textAlignOffsetFunctions.end = textAlignOffsetFunctions.right; 25 | 26 | var textBaselineOffsetFunctions = { 27 | top : function (ascender, descender) { return ascender; }, 28 | middle : function (ascender, descender) { return ((ascender + descender) / 2 - descender); }, 29 | alphabetic : noop, // no-op 30 | bottom : function (ascender, descender) { return -descender; }, 31 | hanging : null, 32 | ideographic: null 33 | }; 34 | // There doesn't seem to be sufficient information on freetype for these 35 | textBaselineOffsetFunctions.hanging = 36 | textBaselineOffsetFunctions.ideographic = 37 | textBaselineOffsetFunctions.top; 38 | 39 | // No defaults: Context must initialize this (with its defaults). 40 | var textAlignOffset, textBaselineOffset, direction; 41 | 42 | var setTextAlign = rendering.setTextAlign = function (textAlign) { 43 | var newFn = textAlignOffsetFunctions[textAlign]; 44 | if (newFn) { 45 | textAlignOffset = newFn; 46 | } 47 | return !!newFn; 48 | }; 49 | 50 | var setTextBaseline = rendering.setTextBaseline = function (textBaseline) { 51 | var newFn = textBaselineOffsetFunctions[textBaseline]; 52 | if (newFn) { 53 | textBaselineOffset = newFn; 54 | } 55 | return !!newFn; 56 | }; 57 | 58 | var setDirection = rendering.setDirection = function (newDirection) { 59 | // 'inherit' value is not supported 60 | if (newDirection === 'inherit') newDirection = 'ltr'; 61 | if (newDirection === 'ltr' || newDirection === 'rtl') 62 | direction = newDirection; 63 | return direction; 64 | }; 65 | 66 | // textwidth returns the width of a text string at the specified font and size. 67 | // derived from http://web.archive.org/web/20070808195131/http://developer.hybrid.fi/font2openvg/renderFont.cpp.txt 68 | var textWidth = rendering.textWidth = function (text, font, size) { 69 | var tw = 0.0; 70 | for (var i = 0; i < text.length; i++) { 71 | var character = text.charCodeAt(i); 72 | var glyph = font.characterMap[character]; 73 | if (glyph < 0 || glyph === undefined) { 74 | continue; //glyph is undefined 75 | } 76 | tw += size * font.glyphAdvances[glyph] / 65536.0; 77 | } 78 | return tw; 79 | }; 80 | 81 | var measureText = rendering.measureText = function (text, font, pointSize) { 82 | // TODO Make these values read-only 83 | var metrics = { 84 | // x-direction 85 | width: 0, // advance width 86 | actualBoundingBoxLeft: 0, 87 | actualBoundingBoxRight: 0, 88 | 89 | // y-direction 90 | fontBoundingBoxAscent: 0, 91 | fontBoundingBoxDescent: 0, 92 | actualBoundingBoxAscent: 0, 93 | actualBoundingBoxDescent: 0, 94 | emHeightAscent: font.ascender * pointSize / 65536.0, 95 | emHeightDescent: font.descender * pointSize / 65536.0, 96 | hangingBaseline: 0, 97 | alphabeticBaseline: 0, 98 | ideographicBaseline: 0, 99 | 100 | freetypeExtra: null 101 | }; 102 | 103 | metrics.freetypeExtra = { 104 | num_glyphs: font.face.num_glyphs, 105 | family_name: font.face.family_name, 106 | style_name: font.face.style_name, 107 | units_per_EM: font.face.units_per_EM, 108 | ascender: font.face.ascender, 109 | descender: font.face.descender, 110 | height: font.face.height 111 | }; 112 | 113 | if (text.length === 0) { 114 | return metrics; 115 | } 116 | 117 | var xx = 0, yy = 0; 118 | for (var i = 0; i < text.length; i++) { 119 | var character = text.charCodeAt(i); 120 | var glyph = font.characterMap[character]; 121 | if (glyph < 0 || glyph === undefined) { 122 | continue; //glyph is undefined 123 | } 124 | 125 | var bbox = font.glyphBBoxes[glyph]; 126 | if (xx + bbox.minX < metrics.actualBoundingBoxLeft) { 127 | metrics.actualBoundingBoxLeft = xx + bbox.minX; 128 | } 129 | if (xx + bbox.maxX > metrics.actualBoundingBoxRight) { 130 | metrics.actualBoundingBoxRight = xx + bbox.maxX; 131 | } 132 | 133 | if (yy + bbox.minY < metrics.actualBoundingBoxDescent) { 134 | metrics.actualBoundingBoxDescent = yy + bbox.minY; 135 | } 136 | if (yy + bbox.maxY > metrics.actualBoundingBoxAscent) { 137 | metrics.actualBoundingBoxAscent = yy + bbox.maxY; 138 | } 139 | 140 | xx += font.glyphAdvances[glyph]; 141 | yy += 0; 142 | } 143 | 144 | metrics.width = xx * pointSize / 65536.0; 145 | 146 | var offsetX = textAlignOffset(metrics.width); 147 | var offsetY = textBaselineOffset(font.ascender, font.descender); 148 | 149 | metrics.actualBoundingBoxLeft *= -pointSize / 65536.0; 150 | metrics.actualBoundingBoxRight *= pointSize / 65536.0; 151 | 152 | metrics.actualBoundingBoxLeft += offsetX; 153 | metrics.actualBoundingBoxRight += offsetX; 154 | 155 | metrics.actualBoundingBoxAscent -= offsetY; 156 | metrics.actualBoundingBoxDescent -= offsetY; 157 | 158 | metrics.actualBoundingBoxAscent *= pointSize / 65536.0; 159 | metrics.actualBoundingBoxDescent *= -pointSize / 65536.0; 160 | 161 | metrics.fontBoundingBoxAscent = metrics.actualBoundingBoxAscent; 162 | metrics.fontBoundingBoxDescent = metrics.actualBoundingBoxDescent; 163 | 164 | // 165 | // These don't seem implemented by freetype 166 | // 167 | // metrics.hangingBaseline *= pointSize / 65536.0; 168 | // metrics.alphabeticBaseline *= pointSize / 65536.0; 169 | // metrics.ideographicBaseline *= pointSize / 65536.0; 170 | 171 | return metrics; 172 | }; 173 | 174 | // This is the "text preparation algorithm" as specified on 175 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-preparation-algorithm 176 | var prepareText = rendering.prepareText = function (text, target, maxWidth) { 177 | var renderInfo = { 178 | text: text, 179 | glyphCount: 0, 180 | glyphPositions: [], 181 | metrics: { 182 | actualBoundingBoxLeft : 0.0, 183 | actualBoundingBoxRight : 0.0, 184 | actualBoundingBoxAscent : 0.0, 185 | actualBoundingBoxDescent : 0.0 186 | }, 187 | scaleX: 1.0, 188 | anchor: { x: 0.0, y: 0.0 }, 189 | physicalAlignment: 'left', 190 | fontInfo: null, 191 | typeface: null 192 | }; 193 | // 1. If maxWidth was provided but is less than or equal to zero, return an 194 | // empty array. 195 | if (maxWidth && maxWidth <= 0) return renderInfo; 196 | 197 | // 2. Replace all the space characters in text with U+0020 SPACE characters. 198 | text = renderInfo.text = text.replace(/\t|\n|\u00lf|\u000a|\u000c|\r/g, ' '); 199 | 200 | // 3. Let font be the current font of target, as given by that object's font 201 | // attribute. 202 | var font = target.font; 203 | 204 | // 4. Apply the appropriate step from the following list to determine the 205 | // value of direction: 206 | // If the target object's direction attribute has the value "ltr" 207 | // Let direction be 'ltr'. 208 | // If the target object's direction attribute has the value "rtl" 209 | // Let direction be 'rtl'. 210 | // If the target object's font style source object is an element 211 | // Let direction be the directionality of the target object's 212 | // font style source object. 213 | // If the target object's font style source object is a Document and 214 | // that Document has a root element child 215 | // Let direction be the directionality of the target object's 216 | // font style source object's root element child. 217 | // Otherwise 218 | // Let direction be 'ltr'. 219 | // Implementor note: This is not included in an html document, so, the 220 | // element rules (3rd and 4th) may be ignored. 221 | var direction = target.direction === 'rtl' ? 'rtl' : 'ltr'; 222 | 223 | // 5. Form a hypothetical infinitely-wide CSS line box containing a single 224 | // inline box containing the text text, with all the properties at their 225 | // initial values except the 'font' property of the inline box set to 226 | // font, the 'direction' property of the inline box set to direction, and 227 | // the 'white-space' property set to 'pre'. [CSS] 228 | 229 | // The target may be a CanvasRenderingContext2D, so, it cannot be assumed 230 | // that fontInfo_ is present (as in DrawingStyle) 231 | var fontInfo = renderInfo.fontInfo = textFns.parseFont(target.font); 232 | textFns.loadTypeface(fontInfo, function (err, loadedTypeface) { 233 | // TODO Handle errors 234 | renderInfo.typeface = loadedTypeface; 235 | }); 236 | var typeface = renderInfo.typeface; 237 | // Going on out of the callback is safe because loadTypeface is synchronous 238 | 239 | var pointSize = fontInfo.size; 240 | var glyphCount = text.length; 241 | var glyphPositions = renderInfo.glyphPositions = new Array(text.length); 242 | var xx = 0, yy = 0, i; 243 | var metrics = renderInfo.metrics; 244 | for (i = 0; i < text.length; i++) { 245 | var character = text.charCodeAt(i); 246 | var glyph = typeface.characterMap[character]; 247 | if (glyph < 0 || glyph === undefined) { 248 | glyphCount--; 249 | continue; //glyph is undefined 250 | } 251 | 252 | var bbox = typeface.glyphBBoxes[glyph]; 253 | if (xx + bbox.minX < metrics.actualBoundingBoxLeft) { 254 | metrics.actualBoundingBoxLeft = xx + bbox.minX; 255 | } 256 | if (xx + bbox.maxX > metrics.actualBoundingBoxRight) { 257 | metrics.actualBoundingBoxRight = xx + bbox.maxX; 258 | } 259 | 260 | if (yy + bbox.minY < metrics.actualBoundingBoxDescent) { 261 | metrics.actualBoundingBoxDescent = yy + bbox.minY; 262 | } 263 | if (yy + bbox.maxY > metrics.actualBoundingBoxAscent) { 264 | metrics.actualBoundingBoxAscent = yy + bbox.maxY; 265 | } 266 | 267 | glyphPositions[i] = { x: xx, y: yy }; 268 | 269 | xx += typeface.glyphAdvances[glyph]; 270 | // yy += 0; // Don't advance on Y, because it's not read from the font 271 | } 272 | var textWidth = xx * pointSize / 65536.0; 273 | renderInfo.glyphCount = glyphCount; 274 | metrics.actualBoundingBoxLeft *= -pointSize / 65536.0; 275 | metrics.actualBoundingBoxRight *= pointSize / 65536.0; 276 | 277 | metrics.actualBoundingBoxAscent *= pointSize / 65536.0; 278 | metrics.actualBoundingBoxDescent *= -pointSize / 65536.0; 279 | 280 | // 6. If maxWidth was provided and the hypothetical width of the inline box 281 | // in the hypothetical line box is greater than maxWidth CSS pixels, then 282 | // change font to have a more condensed font (if one is available or if a 283 | // reasonably readable one can be synthesized by applying a horizontal 284 | // scale factor to the font) or a smaller font, and return to the previous 285 | // step. 286 | if (maxWidth && textWidth > maxWidth) { 287 | // Changing to a condensed font is not supported for now. 288 | // if (typeface.hasCondensedVariant()) { 289 | // // TODO change typeface, recalc glyphPositions 290 | // } else 291 | if (textWidth * MINIMUM_WIDTH_SCALE <= maxWidth) { 292 | // Scale down synthetically 293 | renderInfo.scaleX = maxWidth / textWidth; 294 | textWidth = maxWidth; 295 | } 296 | } 297 | 298 | // 7. The anchor point is a point on the inline box, and the physical 299 | // alignment is one of the values left, right, and center. These variables 300 | // are determined by the textAlign and textBaseline values as follows: 301 | // Horizontal position: 302 | // If textAlign is left 303 | // If textAlign is start and direction is 'ltr' 304 | // If textAlign is end and direction is 'rtl' 305 | // Let the anchor point's horizontal position be the left edge 306 | // of the inline box, and let physical alignment be left. 307 | // If textAlign is right 308 | // If textAlign is end and direction is 'ltr' 309 | // If textAlign is start and direction is 'rtl' 310 | // Let the anchor point's horizontal position be the right 311 | // edge of the inline box, and let physical alignment be 312 | // right. 313 | // If textAlign is center 314 | // Let the anchor point's horizontal position be half way 315 | // between the left and right edges of the inline box, and let 316 | // physical alignment be center. 317 | if (target.textAlign === 'left' || 318 | target.textAlign === 'start' && direction === 'ltr' || 319 | target.textAlign === 'end' && direction === 'rtl') { 320 | renderInfo.anchor.x = 0; 321 | renderInfo.physicalAlignment = 'left'; 322 | } else 323 | if (target.textAlign === 'right' || 324 | target.textAlign === 'end' && direction === 'ltr' || 325 | target.textAlign === 'start' && direction === 'rtl') { 326 | renderInfo.anchor.x = textWidth; 327 | renderInfo.physicalAlignment = 'right'; 328 | } else 329 | if (target.textAlign === 'center') { 330 | renderInfo.anchor.x = textWidth / 2; 331 | renderInfo.physicalAlignment = 'center'; 332 | } 333 | // Vertical position: 334 | // If textBaseline is top 335 | // Let the anchor point's vertical position be the top of the 336 | // em box of the first available font of the inline box. 337 | // If textBaseline is hanging 338 | // Let the anchor point's vertical position be the hanging 339 | // baseline of the first available font of the inline box. 340 | // If textBaseline is middle 341 | // Let the anchor point's vertical position be half way 342 | // between the bottom and the top of the em box of the first 343 | // available font of the inline box. 344 | // If textBaseline is alphabetic 345 | // Let the anchor point's vertical position be the alphabetic 346 | // baseline of the first available font of the inline box. 347 | // If textBaseline is ideographic 348 | // Let the anchor point's vertical position be the 349 | // ideographic baseline of the first available font of the 350 | // inline box. 351 | // If textBaseline is bottom 352 | // Let the anchor point's vertical position be the bottom of 353 | // the em box of the first available font of the inline box. 354 | switch (target.textBaseline) { 355 | case 'top' : 356 | case 'hanging' : // freetype doesn't return the hanging baseline 357 | renderInfo.anchor.y = typeface.ascender * pointSize / 65536.0; 358 | break; 359 | case 'middle' : 360 | renderInfo.anchor.y = ((typeface.ascender + typeface.descender) / 2 - typeface.descender) * pointSize / 65536.0; 361 | break; 362 | case 'alphabetic' : 363 | case 'ideographic' : // freetype doesn't return the ideographic baseline 364 | renderInfo.anchor.y = 0; 365 | break; 366 | case 'bottom' : 367 | renderInfo.anchor.y = -typeface.descender * pointSize / 65536.0; 368 | break; 369 | } 370 | 371 | // 8. Let result be an array constructed by iterating over each glyph in the 372 | // inline box from left to right (if any), adding to the array, for each 373 | // glyph, the shape of the glyph as it is in the inline box, positioned on 374 | // a coordinate space using CSS pixels with its origin is at the anchor 375 | // point. 376 | 377 | for (i = 0; i < glyphCount; i++) { 378 | glyphPositions[i].x = glyphPositions[i].x * pointSize / 65536.0 - renderInfo.anchor.x; 379 | glyphPositions[i].y = glyphPositions[i].y * pointSize / 65536.0 - renderInfo.anchor.y; 380 | } 381 | 382 | // 9. Return result, physical alignment, and the inline box. 383 | return renderInfo; 384 | }; 385 | 386 | // Text renders a string of text at a specified location, size, using the specified font glyphs 387 | // derived from http://web.archive.org/web/20070808195131/http://developer.hybrid.fi/font2openvg/renderFont.cpp.txt 388 | // TODO Use the "text preparation algorithm" 389 | var renderToPath = rendering.renderToPath = function (text, font, size) { 390 | var textPath = vg.createPath(vg.VG_PATH_FORMAT_STANDARD, 391 | vg.VGPathDatatype.VG_PATH_DATATYPE_F, 392 | 1.0, 0.0, 0, 0, /* scale, bias, segment hint, capacity hint */ 393 | vg.VGPathCapabilities.VG_PATH_CAPABILITY_ALL); 394 | 395 | var offset = 0; 396 | vg.loadIdentity(); 397 | vg.scale(size, size); 398 | for (var i = 0; i < text.length; i++) { 399 | var character = text.charCodeAt(i); 400 | var glyph = font.characterMap[character]; 401 | if (glyph < 0 || glyph === undefined) { 402 | continue; //glyph is undefined 403 | } 404 | 405 | vg.transformPath(textPath, font.glyphs[glyph]); 406 | vg.translate(font.glyphAdvances[glyph] / 65536.0, 0); 407 | } 408 | 409 | return textPath; 410 | }; 411 | 412 | var transformRenderToPath = rendering.transformRenderToPath = function (text, renderInfo, transformCallback) { 413 | var textPath = vg.createPath(vg.VG_PATH_FORMAT_STANDARD, 414 | vg.VGPathDatatype.VG_PATH_DATATYPE_F, 415 | 1.0, 0.0, 0, 0, /* scale, bias, segment hint, capacity hint */ 416 | vg.VGPathCapabilities.VG_PATH_CAPABILITY_ALL); 417 | var typeface = renderInfo.typeface; 418 | for (var i = 0; i < text.length; i++) { 419 | var character = text.charCodeAt(i); 420 | var glyph = typeface.characterMap[character]; 421 | if (glyph < 0 || glyph === undefined) { 422 | continue; //glyph is undefined 423 | } 424 | 425 | vg.loadIdentity(); 426 | 427 | transformCallback(textPath, i, character); 428 | } 429 | 430 | return textPath; 431 | }; 432 | 433 | // TODO Use the "text preparation algorithm" 434 | var renderText = rendering.renderText = function (x, y, text, font, size, paintFn) { 435 | var currentMatrix = m.create(), currentLineWidth; 436 | 437 | vg.getMatrix(currentMatrix); 438 | currentLineWidth = vg.getF(vg.VGParamType.VG_STROKE_LINE_WIDTH); 439 | 440 | var mat = m.m( 441 | 1.0, 0.0, 0.0, 442 | 0.0, -1.0, 0.0, 443 | x, y, 1.0 444 | ); 445 | 446 | if (textAlignOffset !== noop) { // no-op is cheap, textWidth isn't 447 | mat[6] += textAlignOffset(textWidth(text, font, size)); 448 | } 449 | 450 | mat[7] += textBaselineOffset(font.ascender * size / 65536.0, 451 | font.descender * size / 65536.0); 452 | 453 | var textPath = renderToPath(text, font, size); 454 | 455 | vg.loadMatrix(currentMatrix); 456 | vg.multMatrix(mat); 457 | paintFn(textPath); 458 | 459 | vg.setF(vg.VGParamType.VG_STROKE_LINE_WIDTH, currentLineWidth); 460 | vg.loadMatrix(currentMatrix); 461 | vg.destroyPath(textPath); 462 | }; 463 | -------------------------------------------------------------------------------- /lib/text/text.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2, node: true */ 2 | "use strict"; 3 | 4 | var text = module.exports; 5 | 6 | var util = require('util'); 7 | 8 | var loading = require('./loading'); 9 | 10 | // The following code is based from 11 | // https://github.com/LearnBoost/node-canvas/blob/master/lib/context2d.js 12 | 13 | var knownFamilies = { 14 | 'sans-serif' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans.ttf', 15 | 'serif' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif.ttf', 16 | 'mono' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono.ttf', 17 | 'sans-serif-bold' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSans-Bold.ttf', 18 | 'serif-bold' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSerif-Bold.ttf', 19 | 'mono-bold' : '/usr/share/fonts/truetype/ttf-dejavu/DejaVuSansMono-Bold.ttf' 20 | }; 21 | 22 | /** 23 | * Cache string values. 24 | */ 25 | var parseFontCache = {}; 26 | 27 | /** 28 | * Font RegExp helpers. 29 | */ 30 | 31 | var weights = 'normal|bold|bolder|lighter|[1-9]00'; 32 | var styles = 'normal|italic|oblique'; 33 | var units = 'px|pt|pc|in|cm|mm|%'; 34 | var string = '\'([^\']+)\'|"([^"]+)"|[\\w-]+'; 35 | 36 | /** 37 | * Font parser RegExp; 38 | */ 39 | 40 | var fontre = new RegExp('^ *' + 41 | '(?:(' + weights + ') *)?' + 42 | '(?:(' + styles + ') *)?' + 43 | '([\\d\\.]+)(' + units + ') *' + 44 | '((?:' + string + ')( *, *(?:' + string + '))*)' 45 | ); 46 | 47 | /** 48 | * Cache typefaces by filename. 49 | */ 50 | var typefaceByFilename = {}; 51 | 52 | /** 53 | * Parse font `str`. 54 | * 55 | * @param {String} str 56 | * @return {Object} 57 | * @api private 58 | */ 59 | 60 | var parseFont = text.parseFont = function (str) { 61 | var font, captures = fontre.exec(str); 62 | 63 | // Invalid 64 | if (!captures) return; 65 | 66 | // Cached 67 | if (parseFontCache[str]) return parseFontCache[str]; 68 | 69 | // Populate font object 70 | font = { 71 | weight : captures[1] || 'normal', 72 | style : captures[2] || 'normal', 73 | specifiedSize : parseFloat(captures[3]), 74 | size : null, 75 | unit : captures[4], 76 | family : captures[5].replace(/["']/g, ''), 77 | typeface : null 78 | }; 79 | 80 | // TODO: dpi 81 | // TODO: remaining unit conversion 82 | switch (font.unit) { 83 | case 'px': 84 | font.size = font.specifiedSize; 85 | break; 86 | case 'pt': 87 | font.size = font.specifiedSize / 0.75; 88 | break; 89 | case 'in': 90 | font.size = font.specifiedSize * 96; 91 | break; 92 | case 'mm': 93 | font.size = font.specifiedSize * 96.0 / 25.4; 94 | break; 95 | case 'cm': 96 | font.size = font.specifiedSize * 96.0 / 2.54; 97 | break; 98 | } 99 | 100 | return parseFontCache[str] = font; 101 | }; 102 | 103 | var loadTypeface = text.loadTypeface = function (font, callback) { 104 | // console.log('Loading typeface: ' + util.inspect(font, false, 1)); 105 | if (font.face) { 106 | callback(undefined, font.face); 107 | return; 108 | } 109 | 110 | var filename = knownFamilies[font.family + '-' + font.weight]; 111 | if (!filename) { 112 | filename = knownFamilies[font.family]; 113 | } 114 | if (!filename) { 115 | filename = knownFamilies['sans-serif']; 116 | } 117 | 118 | var typeface = typefaceByFilename[filename]; 119 | if (typeface) { 120 | font.face = typeface; 121 | callback(undefined, typeface); 122 | return; 123 | } 124 | 125 | console.log('Loading typeface file: ' + filename); 126 | loading.loadFontFileSync(filename, function (err, typeface) { 127 | if (!err) { 128 | font.face = typeface; 129 | typefaceByFilename[filename] = typeface; 130 | } 131 | callback(err, typeface); 132 | }); 133 | }; 134 | 135 | var serialize = text.serialize = function (parsedFont) { 136 | var result = ''; 137 | 138 | // This function must conform to: 139 | // http://dev.w3.org/csswg/cssom/#serializing-css-values 140 | 141 | // Default font is: "normal-weight 10px sans-serif" as per 142 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/the-canvas-element.html#text-styles 143 | 144 | if (parsedFont.style !== 'normal') { 145 | result += parsedFont.style + ' '; 146 | } 147 | 148 | // http://www.w3.org/TR/css3-fonts/#font-weight-prop 149 | if (parsedFont.weight !== 'normal' && parsedFont.weight !== 400) { 150 | result += parsedFont.weight + ' '; 151 | } 152 | 153 | result += parsedFont.specifiedSize + parsedFont.unit; 154 | result += ' ' + parsedFont.family; 155 | 156 | return result; 157 | }; 158 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openvg-canvas", 3 | "preferGlobal" : "true", 4 | "version": "1.1.7", 5 | "author": "Luis Reis (http://luismreis.com/)", 6 | "description": "Canvas on node-openvg", 7 | "bin": { 8 | "node-canvas" : "./bin/node-canvas" 9 | }, 10 | "main": "lib/canvas.js", 11 | "repository": { 12 | "type": "git", 13 | "url": "git://github.com/luismreis/node-openvg-canvas.git" 14 | }, 15 | "keyword": [ 16 | "raspberry-pi", 17 | "openvg", 18 | "canvas" 19 | ], 20 | "dependencies": { 21 | "openvg" : "~0.4.4" 22 | }, 23 | "devDependencies": { 24 | "tap": "*" 25 | }, 26 | "license" : "MIT", 27 | "engines": { 28 | "node": ">=0.8" 29 | }, 30 | "scripts": { 31 | "test": "node node_modules/tap/bin/tap.js tests" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /reference/arcTo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |
6 | 11 | 12 | 13 | 14 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /reference/clipping.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /reference/clippingState.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /reference/ellipse.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 67 | 68 | -------------------------------------------------------------------------------- /reference/images/grido2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/reference/images/grido2.png -------------------------------------------------------------------------------- /reference/images/grido4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eendeego/node-openvg-canvas/ea793b04b685aa33a86dffa8cfe6c17ec1686d43/reference/images/grido4.png -------------------------------------------------------------------------------- /reference/images/noisy_grid.png: -------------------------------------------------------------------------------- 1 | ../../examples/images/noisy_grid.png -------------------------------------------------------------------------------- /reference/javascripts/arcTo.js: -------------------------------------------------------------------------------- 1 | /*jslint indent: 2 */ 2 | 3 | // http://caolanmcmahon.com/posts/writing_for_node_and_the_browser 4 | (function (exports) { 5 | "use strict"; 6 | 7 | var Painter = exports.Painter = function (canvas, options) { 8 | var ctx = canvas.getContext('2d'); 9 | 10 | // This is an approximation to the final ellipse function implementation. 11 | // Strange => Canvas paths cannot have transformations midway 12 | var strangeEllipse = function (x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise) { 13 | ctx.save(); 14 | 15 | ctx.translate(x, y); 16 | ctx.rotate(rotation); 17 | ctx.scale(radiusX, radiusY); 18 | 19 | ctx.moveTo(Math.cos(startAngle), Math.sin(startAngle)); 20 | ctx.arc(0 /* x */, 0 /* y */, 1 /* radius */, startAngle, endAngle, anticlockwise); 21 | 22 | ctx.restore(); 23 | }; 24 | 25 | function arcDraft(p1, p2, p3, radiusX, radiusY, rotation) { 26 | if (radiusY === undefined) { 27 | radiusY = radiusX; 28 | rotation = 0; 29 | } 30 | 31 | var scaleX = radiusY / radiusX; 32 | var cosRotation = Math.cos(-rotation); 33 | var sinRotation = Math.sin(-rotation); 34 | function transform(px, py) { 35 | return { 36 | x: (px * cosRotation - py * sinRotation) * scaleX, 37 | y: px * sinRotation + py * cosRotation 38 | }; 39 | } 40 | function reverseTransform(px, py) { 41 | return { 42 | x: px * cosRotation / scaleX + py * sinRotation, 43 | y: -px * sinRotation / scaleX + py * cosRotation 44 | }; 45 | } 46 | 47 | var p1_ = transform(p1.x, p1.y); 48 | var p2_ = transform(p2.x, p2.y); 49 | var p3_ = transform(p3.x, p3.y); 50 | 51 | var v1 = { x: p2_.x - p1_.x, y: p2_.y - p1_.y }; 52 | var v2 = { x: p3_.x - p2_.x, y: p3_.y - p2_.y }; 53 | var mod_v1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y); 54 | var mod_v2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y); 55 | 56 | var dot_prod = v1.x * v2.x + v1.y * v2.y; 57 | var cross_prod = v1.x * v2.y - v1.y * v2.x; 58 | var sign = cross_prod > 0 ? 1 : -1; 59 | var cos_alpha = dot_prod / (mod_v1 * mod_v2); 60 | var sin_alpha = Math.sqrt(1 - cos_alpha * cos_alpha); 61 | 62 | // tan(x) = sin(2x) / (cos(2x) + 1) 63 | // tan(x/2) = sin(x) / (cos(x) + 1) 64 | // r = t * tan(alpha/2) 65 | // t = rY * sin_alpha / (1 + cos_alpha); 66 | var t = radiusY * sin_alpha / (1 + cos_alpha); 67 | var pstart_ = { x: p2_.x - t * v1.x / mod_v1, y: p2_.y - t * v1.y / mod_v1 }; 68 | var pstart = reverseTransform(pstart_.x, pstart_.y); 69 | 70 | var pend_ = { x: p2_.x + t * v2.x / mod_v2, y: p2_.y + t * v2.y / mod_v2 }; 71 | var pend = reverseTransform(pend_.x, pend_.y); 72 | 73 | // To use OpenVG [s/l][c/cc]arc_to erything needed is already here 74 | // s / l = always short 75 | // cc = sign < 0 76 | 77 | var center_ = { x: pstart_.x - sign * radiusY * v1.y / mod_v1, 78 | y: pstart_.y + sign * radiusY * v1.x / mod_v1 }; 79 | 80 | var center = reverseTransform(center_.x, center_.y); 81 | 82 | v1 = reverseTransform(v1.x, v1.y); 83 | v2 = reverseTransform(v2.x, v2.y); 84 | mod_v1 = Math.sqrt(v1.x * v1.x + v1.y * v1.y); 85 | mod_v2 = Math.sqrt(v2.x * v2.x + v2.y * v2.y); 86 | 87 | // baselines 88 | ctx.lineWidth = 2; 89 | ctx.strokeStyle = 'red'; 90 | 91 | ctx.beginPath(); 92 | ctx.moveTo(p1.x - 20 * v1.x / mod_v1, p1.y - 20 * v1.y / mod_v1); 93 | ctx.lineTo(p2.x + 20 * v1.x / mod_v1, p2.y + 20 * v1.y / mod_v1); 94 | ctx.moveTo(p2.x - 20 * v2.x / mod_v2, p2.y - 20 * v2.y / mod_v2); 95 | ctx.lineTo(p3.x + 20 * v2.x / mod_v2, p3.y + 20 * v2.y / mod_v2); 96 | ctx.stroke(); 97 | 98 | var rc = 4; 99 | 100 | if (cross_prod !== 0) { 101 | // radius 102 | ctx.strokeStyle = '#00f'; 103 | ctx.beginPath(); 104 | if (radiusX === radiusY) { 105 | ctx.moveTo(center.x - radiusX * Math.sqrt(2) / 2, 106 | center.y + radiusY * Math.sqrt(2) / 2); 107 | ctx.lineTo(center.x, center.y); 108 | } else { 109 | ctx.moveTo(center.x + radiusX * Math.cos(Math.PI + rotation), 110 | center.y + radiusX * Math.sin(Math.PI + rotation)); 111 | ctx.lineTo(center.x, center.y); 112 | ctx.lineTo(center.x + radiusY * Math.cos(Math.PI / 2 + rotation), 113 | center.y + radiusY * Math.sin(Math.PI / 2 + rotation)); 114 | } 115 | ctx.stroke(); 116 | 117 | // start, end angles 118 | ctx.strokeStyle = '#0080ff'; 119 | var start_angle = Math.atan2(pstart_.y - center_.y, pstart_.x - center_.x); 120 | var end_angle = Math.atan2(pend_.y - center_.y, pend_.x - center_.x); 121 | ctx.beginPath(); 122 | ctx.moveTo(pstart.x, pstart.y); 123 | ctx.lineTo(center.x, center.y); 124 | ctx.lineTo(pend.x, pend.y); 125 | ctx.stroke(); 126 | 127 | // base circle 128 | ctx.strokeStyle = '#888'; 129 | ctx.beginPath(); 130 | ctx.moveTo(center.x + radiusX, center.y); 131 | strangeEllipse(center.x, center.y, radiusX, radiusY, rotation, 0, 2 * Math.PI, true); 132 | ctx.stroke(); 133 | 134 | ctx.globalAlpha = 0.5; 135 | ctx.lineCap = 'round'; 136 | ctx.strokeStyle = '#00de28'; 137 | ctx.lineWidth = 2 * (rc + 2); 138 | 139 | // leading line (x0, y0 -> startx, starty) 140 | ctx.beginPath(); 141 | ctx.moveTo(p1.x, p1.y); 142 | ctx.lineTo(pstart.x, pstart.y); 143 | ctx.stroke(); 144 | 145 | // the arc 146 | ctx.beginPath(); 147 | ctx.moveTo(pstart.x, pstart.y); 148 | // ctx.arcTo(p2.x, p2.y, p3.x, p3.y, r); 149 | strangeEllipse(center.x, center.y, 150 | radiusX, radiusY, rotation, start_angle, end_angle, 151 | sign < 0); 152 | ctx.stroke(); 153 | 154 | ctx.globalAlpha = 1.0; 155 | ctx.lineCap = 'butt'; 156 | } 157 | 158 | function dot(p) { 159 | ctx.moveTo(p.x + rc, p.y); 160 | ctx.arc(p.x, p.y, rc, 0, 2 * Math.PI, true); 161 | } 162 | 163 | ctx.fillStyle = 'white'; 164 | ctx.lineWidth = 1; 165 | 166 | // control points 167 | ctx.beginPath(); 168 | dot(p1); 169 | dot(p2); 170 | dot(p3); 171 | if (cross_prod !== 0) { dot(center); } 172 | ctx.fill(); 173 | 174 | if (cross_prod !== 0) { 175 | ctx.fillStyle = '#0f0'; 176 | ctx.beginPath(); 177 | dot(pstart); 178 | dot(pend); 179 | ctx.fill(); 180 | } 181 | } 182 | 183 | var radiusX = 20; 184 | var radiusY = 20; 185 | var rotation = 0; 186 | 187 | function getRadiusX() { return radiusX; } 188 | function setRadiusX(r) { radiusX = r; } 189 | function getRadiusY() { return radiusY; } 190 | function setRadiusY(r) { radiusY = r; } 191 | function getRotation() { return rotation; } 192 | function setRotation(r) { rotation = r; } 193 | 194 | Object.defineProperty(this, 'radiusX', { enumerable: true, get: getRadiusX, set: setRadiusX }); 195 | Object.defineProperty(this, 'radiusY', { enumerable: true, get: getRadiusY, set: setRadiusY }); 196 | Object.defineProperty(this, 'rotation', { enumerable: true, get: getRotation, set: setRotation }); 197 | 198 | this.paint = function (now) { 199 | ctx.fillStyle = 'black'; 200 | ctx.fillRect(0, 0, canvas.width, canvas.height); 201 | ctx.save(); 202 | // ctx.translate(0.5, 0.5); 203 | // ctx.scale(3, 3); 204 | 205 | var p = [{ x: 400 - 40, y: 100}, { x: 400, y: 100}, { x: 400, y: 140}]; 206 | var deltaT = now % 6000; 207 | if (deltaT < 1000) { 208 | p[0].x += deltaT / 40; 209 | } else if (deltaT < 2000) { 210 | p[0].x += 25 - (deltaT - 1000) / 40; 211 | } else if (deltaT < 3000) { 212 | p[1].x += (deltaT - 2000) / 50; 213 | p[1].y -= (deltaT - 2000) / 50; 214 | } else if (deltaT < 4000) { 215 | p[1].x += 20 - (deltaT - 3000) / 50; 216 | p[1].y -= 20 - (deltaT - 3000) / 50; 217 | } else if (deltaT < 5000) { 218 | p[2].x -= (deltaT - 4000) / 50; 219 | p[2].y -= (deltaT - 4000) / 50; 220 | } else { 221 | p[2].x -= 20 - (deltaT - 5000) / 50; 222 | p[2].y -= 20 - (deltaT - 5000) / 50; 223 | } 224 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 225 | 226 | var rr = 40; 227 | var center; 228 | function controlPoint(angle) { 229 | return { x: center.x + rr * Math.cos(Math.PI * angle), 230 | y: center.y + rr * Math.sin(Math.PI * angle) }; 231 | } 232 | 233 | center = { x: 100, y: 100 }; 234 | p = [controlPoint(5 / 4), center, controlPoint(deltaT / 3000)]; 235 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 236 | 237 | center = { x: 200, y: 100 }; 238 | p = [controlPoint(1), center, controlPoint(deltaT / 3000)]; 239 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 240 | 241 | center = { x: 300, y: 100 }; 242 | p = [controlPoint(3 / 2), center, controlPoint(deltaT / 3000)]; 243 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 244 | 245 | 246 | center = { x: 100, y: 200 }; 247 | p = [controlPoint(deltaT / 3000), center, controlPoint(5 / 4)]; 248 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 249 | 250 | center = { x: 200, y: 200 }; 251 | p = [controlPoint(deltaT / 3000), center, controlPoint(1)]; 252 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 253 | 254 | center = { x: 300, y: 200 }; 255 | p = [controlPoint(deltaT / 3000), center, controlPoint(3 / 2)]; 256 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 257 | 258 | center = { x: 100, y: 300 }; 259 | p = [controlPoint(5 / 4), controlPoint(deltaT / 3000), controlPoint(0)]; 260 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 261 | 262 | center = { x: 200, y: 300 }; 263 | p = [controlPoint(1), controlPoint(deltaT / 3000), controlPoint(0)]; 264 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 265 | 266 | center = { x: 300, y: 300 }; 267 | p = [controlPoint(3 / 2), controlPoint(deltaT / 3000), controlPoint(0)]; 268 | arcDraft(p[0], p[1], p[2], radiusX, radiusY, rotation); 269 | 270 | 271 | ctx.restore(); 272 | }; 273 | }; 274 | })(typeof exports === 'undefined' ? this['arcTo'] = {} : exports); 275 | -------------------------------------------------------------------------------- /reference/javascripts/requestAnimationFrame.js: -------------------------------------------------------------------------------- 1 | // http://paulirish.com/2011/requestanimationframe-for-smart-animating/ 2 | 3 | (function() { 4 | var lastTime = 0; 5 | var vendors = ['ms', 'moz', 'webkit', 'o']; 6 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 7 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 8 | window.cancelAnimationFrame = 9 | window[vendors[x]+'CancelAnimationFrame'] || window[vendors[x]+'CancelRequestAnimationFrame']; 10 | } 11 | 12 | if (!window.requestAnimationFrame) 13 | window.requestAnimationFrame = function(callback, element) { 14 | var currTime = new Date().getTime(); 15 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 16 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 17 | timeToCall); 18 | lastTime = currTime + timeToCall; 19 | return id; 20 | }; 21 | 22 | if (!window.cancelAnimationFrame) 23 | window.cancelAnimationFrame = function(id) { 24 | clearTimeout(id); 25 | }; 26 | }()); 27 | -------------------------------------------------------------------------------- /reference/lineDash.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /reference/patterns.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 103 | 104 | 105 | -------------------------------------------------------------------------------- /reference/shadows.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | Noisy grid found at SubtlePatterns. 10 | 11 | 12 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /reference/stylesheets/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #303030; 3 | font-family: sans-serif; 4 | font-size: 12px; 5 | color: #c0c0c0; 6 | } 7 | 8 | a, a:hover, a:active, a:visited { 9 | color: #4080ff; 10 | } 11 | -------------------------------------------------------------------------------- /src/freeimage/freeimage.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | #include 7 | #include "../v8_helpers.h" 8 | 9 | #include 10 | 11 | #include "freeimage.h" 12 | #include "image.h" 13 | 14 | using namespace std; 15 | using namespace v8; 16 | using namespace node; 17 | 18 | namespace openvg_canvas { 19 | namespace freeimage { 20 | 21 | ISOLATE(freeimage_isolate) 22 | 23 | void FreeImage::Initialize(Handle target) { 24 | ISOLATE_INIT(freeimage_isolate) 25 | SCOPE(freeimage_isolate); 26 | 27 | // Prepare constructor template 28 | Local constructor = FunctionTemplate::New(New); 29 | constructor->SetClassName(String::NewSymbol("FreeImage")); 30 | constructor->InstanceTemplate()->SetInternalFieldCount(1); 31 | 32 | // Prototype 33 | Local