canvas.js 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319
  1. 'use strict';
  2. /*!
  3. * Canvas
  4. * Copyright (c) 2010 LearnBoost <tj@learnboost.com>
  5. * MIT Licensed
  6. */
  7. /**
  8. * Module dependencies.
  9. */
  10. var canvas = require('./bindings')
  11. , Canvas = canvas.Canvas
  12. , Image = canvas.Image
  13. , cairoVersion = canvas.cairoVersion
  14. , Context2d = require('./context2d')
  15. , PNGStream = require('./pngstream')
  16. , PDFStream = require('./pdfstream')
  17. , JPEGStream = require('./jpegstream')
  18. , FontFace = canvas.FontFace
  19. , fs = require('fs')
  20. , packageJson = require("../package.json")
  21. , FORMATS = ['image/png', 'image/jpeg'];
  22. /**
  23. * Export `Canvas` as the module.
  24. */
  25. var Canvas = exports = module.exports = Canvas;
  26. /**
  27. * Library version.
  28. */
  29. exports.version = packageJson.version;
  30. /**
  31. * Cairo version.
  32. */
  33. exports.cairoVersion = cairoVersion;
  34. /**
  35. * jpeglib version.
  36. */
  37. if (canvas.jpegVersion) {
  38. exports.jpegVersion = canvas.jpegVersion;
  39. }
  40. /**
  41. * gif_lib version.
  42. */
  43. if (canvas.gifVersion) {
  44. exports.gifVersion = canvas.gifVersion.replace(/[^.\d]/g, '');
  45. }
  46. /**
  47. * freetype version.
  48. */
  49. if (canvas.freetypeVersion) {
  50. exports.freetypeVersion = canvas.freetypeVersion;
  51. }
  52. /**
  53. * Expose constructors.
  54. */
  55. exports.Context2d = Context2d;
  56. exports.PNGStream = PNGStream;
  57. exports.PDFStream = PDFStream;
  58. exports.JPEGStream = JPEGStream;
  59. exports.Image = Image;
  60. exports.ImageData = canvas.ImageData;
  61. if (FontFace) {
  62. var Font = function Font(name, path, idx) {
  63. this.name = name;
  64. this._faces = {};
  65. this.addFace(path, 'normal', 'normal', idx);
  66. };
  67. Font.prototype.addFace = function(path, weight, style, idx) {
  68. style = style || 'normal';
  69. weight = weight || 'normal';
  70. var face = new FontFace(path, idx || 0);
  71. this._faces[weight + '-' + style] = face;
  72. };
  73. Font.prototype.getFace = function(weightStyle) {
  74. return this._faces[weightStyle] || this._faces['normal-normal'];
  75. };
  76. exports.Font = Font;
  77. }
  78. /**
  79. * Context2d implementation.
  80. */
  81. require('./context2d');
  82. /**
  83. * Image implementation.
  84. */
  85. require('./image');
  86. /**
  87. * Inspect canvas.
  88. *
  89. * @return {String}
  90. * @api public
  91. */
  92. Canvas.prototype.inspect = function(){
  93. return '[Canvas ' + this.width + 'x' + this.height + ']';
  94. };
  95. /**
  96. * Get a context object.
  97. *
  98. * @param {String} contextId
  99. * @return {Context2d}
  100. * @api public
  101. */
  102. Canvas.prototype.getContext = function(contextId){
  103. if ('2d' == contextId) {
  104. var ctx = this._context2d || (this._context2d = new Context2d(this));
  105. this.context = ctx;
  106. ctx.canvas = this;
  107. return ctx;
  108. }
  109. };
  110. /**
  111. * Create a `PNGStream` for `this` canvas.
  112. *
  113. * @return {PNGStream}
  114. * @api public
  115. */
  116. Canvas.prototype.pngStream =
  117. Canvas.prototype.createPNGStream = function(){
  118. return new PNGStream(this);
  119. };
  120. /**
  121. * Create a synchronous `PNGStream` for `this` canvas.
  122. *
  123. * @return {PNGStream}
  124. * @api public
  125. */
  126. Canvas.prototype.syncPNGStream =
  127. Canvas.prototype.createSyncPNGStream = function(){
  128. return new PNGStream(this, true);
  129. };
  130. /**
  131. * Create a `PDFStream` for `this` canvas.
  132. *
  133. * @return {PDFStream}
  134. * @api public
  135. */
  136. Canvas.prototype.pdfStream =
  137. Canvas.prototype.createPDFStream = function(){
  138. return new PDFStream(this);
  139. };
  140. /**
  141. * Create a synchronous `PDFStream` for `this` canvas.
  142. *
  143. * @return {PDFStream}
  144. * @api public
  145. */
  146. Canvas.prototype.syncPDFStream =
  147. Canvas.prototype.createSyncPDFStream = function(){
  148. return new PDFStream(this, true);
  149. };
  150. /**
  151. * Create a `JPEGStream` for `this` canvas.
  152. *
  153. * @param {Object} options
  154. * @return {JPEGStream}
  155. * @api public
  156. */
  157. Canvas.prototype.jpegStream =
  158. Canvas.prototype.createJPEGStream = function(options){
  159. return this.createSyncJPEGStream(options);
  160. };
  161. /**
  162. * Create a synchronous `JPEGStream` for `this` canvas.
  163. *
  164. * @param {Object} options
  165. * @return {JPEGStream}
  166. * @api public
  167. */
  168. Canvas.prototype.syncJPEGStream =
  169. Canvas.prototype.createSyncJPEGStream = function(options){
  170. options = options || {};
  171. // Don't allow the buffer size to exceed the size of the canvas (#674)
  172. var maxBufSize = this.width * this.height * 4;
  173. var clampedBufSize = Math.min(options.bufsize || 4096, maxBufSize);
  174. return new JPEGStream(this, {
  175. bufsize: clampedBufSize
  176. , quality: options.quality || 75
  177. , progressive: options.progressive || false
  178. });
  179. };
  180. /**
  181. * Return a data url. Pass a function for async support (required for "image/jpeg").
  182. *
  183. * @param {String} type, optional, one of "image/png" or "image/jpeg", defaults to "image/png"
  184. * @param {Object|Number} encoderOptions, optional, options for jpeg compression (see documentation for Canvas#jpegStream) or the JPEG encoding quality from 0 to 1.
  185. * @param {Function} fn, optional, callback for asynchronous operation. Required for type "image/jpeg".
  186. * @return {String} data URL if synchronous (callback omitted)
  187. * @api public
  188. */
  189. Canvas.prototype.toDataURL = function(a1, a2, a3){
  190. // valid arg patterns (args -> [type, opts, fn]):
  191. // [] -> ['image/png', null, null]
  192. // [qual] -> ['image/png', null, null]
  193. // [undefined] -> ['image/png', null, null]
  194. // ['image/png'] -> ['image/png', null, null]
  195. // ['image/png', qual] -> ['image/png', null, null]
  196. // [fn] -> ['image/png', null, fn]
  197. // [type, fn] -> [type, null, fn]
  198. // [undefined, fn] -> ['image/png', null, fn]
  199. // ['image/png', qual, fn] -> ['image/png', null, fn]
  200. // ['image/jpeg', fn] -> ['image/jpeg', null, fn]
  201. // ['image/jpeg', opts, fn] -> ['image/jpeg', opts, fn]
  202. // ['image/jpeg', qual, fn] -> ['image/jpeg', {quality: qual}, fn]
  203. // ['image/jpeg', undefined, fn] -> ['image/jpeg', null, fn]
  204. var type = 'image/png';
  205. var opts = {};
  206. var fn;
  207. if ('function' === typeof a1) {
  208. fn = a1;
  209. } else {
  210. if ('string' === typeof a1 && FORMATS.indexOf(a1.toLowerCase()) !== -1) {
  211. type = a1.toLowerCase();
  212. }
  213. if ('function' === typeof a2) {
  214. fn = a2;
  215. } else {
  216. if ('object' === typeof a2) {
  217. opts = a2;
  218. } else if ('number' === typeof a2) {
  219. opts = {quality: Math.max(0, Math.min(1, a2)) * 100};
  220. }
  221. if ('function' === typeof a3) {
  222. fn = a3;
  223. } else if (undefined !== a3) {
  224. throw new TypeError(typeof a3 + ' is not a function');
  225. }
  226. }
  227. }
  228. if (this.width === 0 || this.height === 0) {
  229. // Per spec, if the bitmap has no pixels, return this string:
  230. var str = "data:,";
  231. if (fn) {
  232. setTimeout(function() {
  233. fn(null, str);
  234. });
  235. }
  236. return str;
  237. }
  238. if ('image/png' === type) {
  239. if (fn) {
  240. this.toBuffer(function(err, buf){
  241. if (err) return fn(err);
  242. fn(null, 'data:image/png;base64,' + buf.toString('base64'));
  243. });
  244. } else {
  245. return 'data:image/png;base64,' + this.toBuffer().toString('base64');
  246. }
  247. } else if ('image/jpeg' === type) {
  248. if (undefined === fn) {
  249. throw new Error('Missing required callback function for format "image/jpeg"');
  250. }
  251. var stream = this.jpegStream(opts);
  252. // note that jpegStream is synchronous
  253. var buffers = [];
  254. stream.on('data', function (chunk) {
  255. buffers.push(chunk);
  256. });
  257. stream.on('error', function (err) {
  258. fn(err);
  259. });
  260. stream.on('end', function() {
  261. var result = 'data:image/jpeg;base64,' + Buffer.concat(buffers).toString('base64');
  262. fn(null, result);
  263. });
  264. }
  265. };