skins.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. var logging = require("./logging");
  2. var lwip = require("@randy.tarampi/lwip");
  3. var fs = require("fs");
  4. var exp = {};
  5. // extracts the face from an image +buffer+
  6. // result is saved to a file called +outname+
  7. // callback: error
  8. exp.extract_face = function(buffer, outname, callback) {
  9. lwip.open(buffer, "png", function(err, image) {
  10. if (err) {
  11. callback(err);
  12. } else {
  13. image.batch()
  14. .crop(8, 8, 15, 15) // face
  15. .opacify() // remove transparency
  16. .writeFile(outname, function(write_err) {
  17. if (write_err) {
  18. callback(write_err);
  19. } else {
  20. callback(null);
  21. }
  22. });
  23. }
  24. });
  25. };
  26. // extracts the helm from an image +buffer+ and lays it over a +facefile+
  27. // +facefile+ is the filename of an image produced by extract_face
  28. // result is saved to a file called +outname+
  29. // callback: error
  30. exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
  31. lwip.open(buffer, "png", function(err, skin_img) {
  32. if (err) {
  33. callback(err);
  34. } else {
  35. lwip.open(facefile, function(open_err, face_img) {
  36. if (open_err) {
  37. callback(open_err);
  38. } else {
  39. face_img.toBuffer("png", { compression: "none" }, function(buf_err, face_buffer) {
  40. if (buf_err) {
  41. callback(buf_err);
  42. } else {
  43. // crop to hat transparency-bounding-box
  44. skin_img.crop(32, 0, 63, 31, function(area_err, helm_area) {
  45. if (area_err) {
  46. callback(area_err);
  47. } else {
  48. /* eslint-disable no-labels */
  49. var is_opaque = true;
  50. if (skin_img.__trans) { // eslint-disable-line no-underscore-dangle
  51. xloop:
  52. for (var x = 0; x < helm_area.width(); x++) {
  53. for (var y = 0; y < helm_area.height(); y++) {
  54. // check if transparency-bounding-box has transparency
  55. if (helm_area.getPixel(x, y).a !== 100) {
  56. is_opaque = false;
  57. break xloop;
  58. }
  59. }
  60. }
  61. /* eslint-enable no-labels */
  62. } else {
  63. is_opaque = true;
  64. }
  65. skin_img.crop(8, 8, 15, 15, function(crop_err, helm_img) {
  66. if (crop_err) {
  67. callback(crop_err);
  68. } else {
  69. face_img.paste(0, 0, helm_img, function(img_err, face_helm_img) {
  70. if (img_err) {
  71. callback(img_err);
  72. } else {
  73. if (is_opaque) {
  74. logging.debug(rid, "Skin is not transparent, skipping helm!");
  75. callback(null);
  76. } else {
  77. face_helm_img.toBuffer("png", {compression: "none"}, function(buf_err2, face_helm_buffer) {
  78. if (buf_err2) {
  79. callback(buf_err2);
  80. } else {
  81. if (face_helm_buffer.toString() !== face_buffer.toString()) {
  82. face_helm_img.writeFile(outname, function(write_err) {
  83. callback(write_err);
  84. });
  85. } else {
  86. logging.debug(rid, "helm img == face img, not storing!");
  87. callback(null);
  88. }
  89. }
  90. });
  91. }
  92. }
  93. });
  94. }
  95. });
  96. }
  97. });
  98. }
  99. });
  100. }
  101. });
  102. }
  103. });
  104. };
  105. // resizes the image file +inname+ to +size+ by +size+ pixels
  106. // callback: error, image buffer
  107. exp.resize_img = function(inname, size, callback) {
  108. lwip.open(inname, function(err, image) {
  109. if (err) {
  110. callback(err, null);
  111. } else {
  112. image.batch()
  113. .resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
  114. .toBuffer("png", function(buf_err, buffer) {
  115. if (buf_err) {
  116. callback(buf_err, null);
  117. } else {
  118. callback(null, buffer);
  119. }
  120. });
  121. }
  122. });
  123. };
  124. // returns "mhf_alex" or "mhf_steve" calculated by the +uuid+
  125. exp.default_skin = function(uuid) {
  126. // great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
  127. // https://git.io/xJpV
  128. // MC uses `uuid.hashCode() & 1` for alex
  129. // that can be compacted to counting the LSBs of every 4th byte in the UUID
  130. // an odd sum means alex, an even sum means steve
  131. // XOR-ing all the LSBs gives us 1 for alex and 0 for steve
  132. var lsbs_even = parseInt(uuid[ 7], 16) ^
  133. parseInt(uuid[15], 16) ^
  134. parseInt(uuid[23], 16) ^
  135. parseInt(uuid[31], 16);
  136. return lsbs_even ? "mhf_alex" : "mhf_steve";
  137. };
  138. // helper method for opening a skin file from +skinpath+
  139. // callback: error, image buffer
  140. exp.open_skin = function(rid, skinpath, callback) {
  141. fs.readFile(skinpath, function(err, buf) {
  142. if (err) {
  143. callback(err, null);
  144. } else {
  145. callback(null, buf);
  146. }
  147. });
  148. };
  149. // write the image +buffer+ to the +outpath+ file
  150. // the image is stripped down by lwip.
  151. // callback: error
  152. exp.save_image = function(buffer, outpath, callback) {
  153. lwip.open(buffer, "png", function(err, image) {
  154. if (err) {
  155. callback(err);
  156. } else {
  157. image.writeFile(outpath, function(write_err) {
  158. if (write_err) {
  159. callback(write_err);
  160. } else {
  161. callback(null);
  162. }
  163. });
  164. }
  165. });
  166. };
  167. module.exports = exp;