png.js 4.3 KB

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