png.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. 'use strict';
  2. var util = require('util');
  3. var Stream = require('stream');
  4. var Parser = require('./parser-async');
  5. var Packer = require('./packer-async');
  6. var PNGSync = require('./png-sync');
  7. var PNG = exports.PNG = function(options) {
  8. Stream.call(this);
  9. options = options || {}; // eslint-disable-line no-param-reassign
  10. // coerce pixel dimensions to integers (also coerces undefined -> 0):
  11. this.width = options.width | 0;
  12. this.height = options.height | 0;
  13. this.data = this.width > 0 && this.height > 0 ?
  14. new Buffer(4 * this.width * this.height) : null;
  15. if (options.fill && this.data) {
  16. this.data.fill(0);
  17. }
  18. this.gamma = 0;
  19. this.readable = this.writable = true;
  20. this._parser = new Parser(options);
  21. this._parser.on('error', this.emit.bind(this, 'error'));
  22. this._parser.on('close', this._handleClose.bind(this));
  23. this._parser.on('metadata', this._metadata.bind(this));
  24. this._parser.on('gamma', this._gamma.bind(this));
  25. this._parser.on('parsed', function(data) {
  26. this.data = data;
  27. this.emit('parsed', data);
  28. }.bind(this));
  29. this._packer = new Packer(options);
  30. this._packer.on('data', this.emit.bind(this, 'data'));
  31. this._packer.on('end', this.emit.bind(this, 'end'));
  32. this._parser.on('close', this._handleClose.bind(this));
  33. this._packer.on('error', this.emit.bind(this, 'error'));
  34. };
  35. util.inherits(PNG, Stream);
  36. PNG.sync = PNGSync;
  37. PNG.prototype.pack = function() {
  38. if (!this.data || !this.data.length) {
  39. this.emit('error', 'No data provided');
  40. return this;
  41. }
  42. process.nextTick(function() {
  43. this._packer.pack(this.data, this.width, this.height, this.gamma);
  44. }.bind(this));
  45. return this;
  46. };
  47. PNG.prototype.parse = function(data, callback) {
  48. if (callback) {
  49. var onParsed, onError;
  50. onParsed = function(parsedData) {
  51. this.removeListener('error', onError);
  52. this.data = parsedData;
  53. callback(null, this);
  54. }.bind(this);
  55. onError = function(err) {
  56. this.removeListener('parsed', onParsed);
  57. callback(err, null);
  58. }.bind(this);
  59. this.once('parsed', onParsed);
  60. this.once('error', onError);
  61. }
  62. this.end(data);
  63. return this;
  64. };
  65. PNG.prototype.write = function(data) {
  66. this._parser.write(data);
  67. return true;
  68. };
  69. PNG.prototype.end = function(data) {
  70. this._parser.end(data);
  71. };
  72. PNG.prototype._metadata = function(metadata) {
  73. this.width = metadata.width;
  74. this.height = metadata.height;
  75. this.emit('metadata', metadata);
  76. };
  77. PNG.prototype._gamma = function(gamma) {
  78. this.gamma = gamma;
  79. };
  80. PNG.prototype._handleClose = function() {
  81. if (!this._parser.writable && !this._packer.readable) {
  82. this.emit('close');
  83. }
  84. };
  85. PNG.bitblt = function(src, dst, srcX, srcY, width, height, deltaX, deltaY) { // eslint-disable-line max-params
  86. // coerce pixel dimensions to integers (also coerces undefined -> 0):
  87. /* eslint-disable no-param-reassign */
  88. srcX |= 0;
  89. srcY |= 0;
  90. width |= 0;
  91. height |= 0;
  92. deltaX |= 0;
  93. deltaY |= 0;
  94. /* eslint-enable no-param-reassign */
  95. if (srcX > src.width || srcY > src.height || srcX + width > src.width || srcY + height > src.height) {
  96. throw new Error('bitblt reading outside image');
  97. }
  98. if (deltaX > dst.width || deltaY > dst.height || deltaX + width > dst.width || deltaY + height > dst.height) {
  99. throw new Error('bitblt writing outside image');
  100. }
  101. for (var y = 0; y < height; y++) {
  102. src.data.copy(dst.data,
  103. ((deltaY + y) * dst.width + deltaX) << 2,
  104. ((srcY + y) * src.width + srcX) << 2,
  105. ((srcY + y) * src.width + srcX + width) << 2
  106. );
  107. }
  108. };
  109. PNG.prototype.bitblt = function(dst, srcX, srcY, width, height, deltaX, deltaY) { // eslint-disable-line max-params
  110. PNG.bitblt(this, dst, srcX, srcY, width, height, deltaX, deltaY);
  111. return this;
  112. };
  113. PNG.adjustGamma = function(src) {
  114. if (src.gamma) {
  115. for (var y = 0; y < src.height; y++) {
  116. for (var x = 0; x < src.width; x++) {
  117. var idx = (src.width * y + x) << 2;
  118. for (var i = 0; i < 3; i++) {
  119. var sample = src.data[idx + i] / 255;
  120. sample = Math.pow(sample, 1 / 2.2 / src.gamma);
  121. src.data[idx + i] = Math.round(sample * 255);
  122. }
  123. }
  124. }
  125. src.gamma = 0;
  126. }
  127. };
  128. PNG.prototype.adjustGamma = function() {
  129. PNG.adjustGamma(this);
  130. };