crafatar/crafatar

View on GitHub
lib/skins.js

Summary

Maintainability
C
7 hrs
Test Coverage
var logging = require("./logging");
var lwip = require("@randy.tarampi/lwip");
var fs = require("fs");

var exp = {};

// extracts the face from an image +buffer+
// result is saved to a file called +outname+
// callback: error
exp.extract_face = function(buffer, outname, callback) {
  lwip.open(buffer, "png", function(err, image) {
    if (err) {
      callback(err);
    } else {
      image.batch()
      .crop(8, 8, 15, 15) // face
      .opacify()          // remove transparency
      .writeFile(outname, function(write_err) {
        if (write_err) {
          callback(write_err);
        } else {
          callback(null);
        }
      });
    }
  });
};

// extracts the helm from an image +buffer+ and lays it over a +facefile+
// +facefile+ is the filename of an image produced by extract_face
// result is saved to a file called +outname+
// callback: error
exp.extract_helm = function(rid, facefile, buffer, outname, callback) {
  lwip.open(buffer, "png", function(err, skin_img) {
    if (err) {
      callback(err);
    } else {
      lwip.open(facefile, function(open_err, face_img) {
        if (open_err) {
          callback(open_err);
        } else {
          face_img.toBuffer("png", { compression: "none" }, function(buf_err, face_buffer) {
            if (buf_err) {
              callback(buf_err);
            } else {
              // crop to hat transparency-bounding-box
              skin_img.crop(32, 0, 63, 31, function(area_err, helm_area) {
                if (area_err) {
                  callback(area_err);
                } else {
                  /* eslint-disable no-labels */
                  var is_opaque = true;
                  if (skin_img.__trans) { // eslint-disable-line no-underscore-dangle
                    xloop:
                    for (var x = 0; x < helm_area.width(); x++) {
                      for (var y = 0; y < helm_area.height(); y++) {
                        // check if transparency-bounding-box has transparency
                        if (helm_area.getPixel(x, y).a !== 100) {
                          is_opaque = false;
                          break xloop;
                        }
                      }
                    }
                    /* eslint-enable no-labels */
                  } else {
                    is_opaque = true;
                  }
                  skin_img.crop(8, 8, 15, 15, function(crop_err, helm_img) {
                    if (crop_err) {
                      callback(crop_err);
                    } else {
                      face_img.paste(0, 0, helm_img, function(img_err, face_helm_img) {
                        if (img_err) {
                          callback(img_err);
                        } else {
                          if (is_opaque) {
                            logging.debug(rid, "Skin is not transparent, skipping helm!");
                            callback(null);
                          } else {
                            face_helm_img.toBuffer("png", {compression: "none"}, function(buf_err2, face_helm_buffer) {
                              if (buf_err2) {
                                callback(buf_err2);
                              } else {
                                if (face_helm_buffer.toString() !== face_buffer.toString()) {
                                  face_helm_img.writeFile(outname, function(write_err) {
                                    callback(write_err);
                                  });
                                } else {
                                  logging.debug(rid, "helm img == face img, not storing!");
                                  callback(null);
                                }
                              }
                            });
                          }
                        }
                      });
                    }
                  });
                }
              });
            }
          });
        }
      });
    }
  });
};

// resizes the image file +inname+ to +size+ by +size+ pixels
// callback: error, image buffer
exp.resize_img = function(inname, size, callback) {
  lwip.open(inname, function(err, image) {
    if (err) {
      callback(err, null);
    } else {
      image.batch()
      .resize(size, size, "nearest-neighbor") // nearest-neighbor doesn't blur
      .toBuffer("png", function(buf_err, buffer) {
        if (buf_err) {
          callback(buf_err, null);
        } else {
          callback(null, buffer);
        }
      });
    }
  });
};

// returns "mhf_alex" or "mhf_steve" calculated by the +uuid+
exp.default_skin = function(uuid) {
  // great thanks to Minecrell for research into Minecraft and Java's UUID hashing!
  // https://git.io/xJpV
  // MC uses `uuid.hashCode() & 1` for alex
  // that can be compacted to counting the LSBs of every 4th byte in the UUID
  // an odd sum means alex, an even sum means steve
  // XOR-ing all the LSBs gives us 1 for alex and 0 for steve
  var lsbs_even = parseInt(uuid[ 7], 16) ^
                  parseInt(uuid[15], 16) ^
                  parseInt(uuid[23], 16) ^
                  parseInt(uuid[31], 16);
  return lsbs_even ? "mhf_alex" : "mhf_steve";
};

// helper method for opening a skin file from +skinpath+
// callback: error, image buffer
exp.open_skin = function(rid, skinpath, callback) {
  fs.readFile(skinpath, function(err, buf) {
    if (err) {
      callback(err, null);
    } else {
      callback(null, buf);
    }
  });
};

// write the image +buffer+ to the +outpath+ file
// the image is stripped down by lwip.
// callback: error
exp.save_image = function(buffer, outpath, callback) {
  lwip.open(buffer, "png", function(err, image) {
    if (err) {
      callback(err);
    } else {
      image.writeFile(outpath, function(write_err) {
        if (write_err) {
          callback(write_err);
        } else {
          callback(null);
        }
      });
    }
  });
};

module.exports = exp;