UNPKG

creatures

Version:

Library to interface with the Creatures 2 game

455 lines (376 loc) 10.2 kB
var Extractor = require('binary-extractor'), libpath = require('path'), Blast = __Protoblast, Fn = Blast.Collection.Function, fs = require('graceful-fs'); // Get the Creatures namespace var Creatures = Fn.getNamespace('Develry.Creatures'); /** * The S16 class * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.0 * @version 0.2.0 * * @param {CreaturesApplication} app * @param {String} path */ var S16 = Fn.inherits('Develry.Creatures.Base', function S16(app, path) { // The parent creatures app this.app = app; // The basename this.basename = libpath.basename(path); // The path to the file this.path = path; // The content images this.images = []; // The filepaths we tried this.tried = []; }); /** * Mean reduce * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.4 * @version 0.2.4 * * @param {Array} values * * @return {Number} */ S16.setStatic(function meanReduce(values) { var result = values[0], i; for (i = 1; i < values.length; i++) { result = (result + values[i]) / 2; } return result; }); /** * Converts an RGB color value to HSL. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes r, g, and b are contained in the set [0, 255] and * returns h, s, and l in the set [0, 1]. * * @param {Number} r The red color value * @param {Number} g The green color value * @param {Number} b The blue color value * * @return {Array} The HSL representation */ S16.setStatic(function rgbToHsl(r, g, b) { r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, l = (max + min) / 2; if (max == min) { h = s = 0; // achromatic } else { var d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min); switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ h, s, l ]; }); /** * Converts an HSL color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 255]. * * @param {Number} h The hue * @param {Number} s The saturation * @param {Number} l The lightness * @return {Array} The RGB representation */ S16.setStatic(function hslToRgb(h, s, l) { var r, g, b; if (s == 0) { r = g = b = l; // achromatic } else { var q = l < 0.5 ? l * (1 + s) : l + s - l * s; var p = 2 * l - q; r = S16.hue2rgb(p, q, h + 1/3); g = S16.hue2rgb(p, q, h); b = S16.hue2rgb(p, q, h - 1/3); } return [ r * 255, g * 255, b * 255 ]; }); /** * Convert hue to rgb */ S16.setStatic(function hue2rgb(p, q, t) { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; }); /** * Converts an RGB color value to HSV. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes r, g, and b are contained in the set [0, 255] and * returns h, s, and v in the set [0, 1]. * * @param {Number} r The red color value * @param {Number} g The green color value * @param {Number} b The blue color value * @return {Array} The HSV representation */ S16.setStatic(function rgbToHsv(r, g, b) { r /= 255, g /= 255, b /= 255; var max = Math.max(r, g, b), min = Math.min(r, g, b); var h, s, v = max; var d = max - min; s = max == 0 ? 0 : d / max; if (max == min) { h = 0; // achromatic } else { switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; case b: h = (r - g) / d + 4; break; } h /= 6; } return [ h, s, v ]; }); /** * Converts an HSV color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes h, s, and v are contained in the set [0, 1] and * returns r, g, and b in the set [0, 255]. * * @param {Number} h The hue * @param {Number} s The saturation * @param {Number} v The value * @return {Array} The RGB representation */ S16.setStatic(function hsvToRgb(h, s, v) { var r, g, b; var i = Math.floor(h * 6); var f = h * 6 - i; var p = v * (1 - s); var q = v * (1 - f * s); var t = v * (1 - (1 - f) * s); switch (i % 6) { case 0: r = v, g = t, b = p; break; case 1: r = q, g = v, b = p; break; case 2: r = p, g = v, b = t; break; case 3: r = p, g = q, b = v; break; case 4: r = t, g = p, b = v; break; case 5: r = v, g = p, b = q; break; } return [ r * 255, g * 255, b * 255 ]; }); /** * Read the file and process it * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.0 * @version 0.2.4 * * @param {Function} callback */ S16.setCacheMethod(function load(callback) { var that = this, got_filename, tried; if (!this.app.hasBeenSeen('process_path')) { return this.app.afterOnce('process_path', function whenReady() { load.call(that, callback); }); } if (!callback) { callback = Fn.thrower; } tried = this.tried; // If the basename if the same as the path, // we actually need to add path information if (this.path == this.basename) { got_filename = true; this.path = libpath.resolve(this.app.process_dir, 'Images', this.path); } this.readFile(this.path, function gotFileBuffer(err, buffer) { var new_path; if (err) { // Maybe try looking for something else? if (err.code == 'ENOENT') { new_path = libpath.resolve(that.app.process_dir, 'Applet Data', that.basename); if (tried.indexOf(new_path) == -1) { that.path = new_path; tried.push(new_path); return that.readFile(new_path, gotFileBuffer); } } return callback(err); } that.processBuffer(buffer); that.emit('loaded'); callback(null, that); }); }); /** * Set the Pigment data * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.4 * @version 0.2.4 * * @param {Object} obj */ S16.setMethod(function setPigment(obj) { this.pigment_info = obj; }); /** * Set the Pigment bleed data * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.4 * @version 0.2.4 * * @param {Array} arr */ S16.setMethod(function setPigmentBleed(arr) { if (!arr || !arr.length) { this.pigment_bleed_info = null; return; } this.pigment_bleed_info = arr; }); /** * Process the buffer * * @author Jelle De Loecker <jelle@develry.be> * @since 0.2.0 * @version 0.2.4 * * @param {Buffer} buffer */ S16.setMethod(function processBuffer(buffer) { var images = this.images, number, entry, pixel, index, info, swap, temp, hue, hsl, rot, ex = new Extractor(), sr, sg, sb, i, j, k, l, r, g, b; // Set the extractor buffer ex.setBuffer(buffer); // Get the RGB pixel format this.format = ex.readLong(); // Get the number of images this.image_count = ex.readWord(); // All of the headers come before the actual data for (i = 0; i < this.image_count; i++) { entry = { index : i, offset : ex.readLong(), width : ex.readWord(), height : ex.readWord() }; images[i] = entry; } // Iterage again to get the data for (i = 0; i < this.image_count; i++) { entry = images[i]; entry.buffer = ex.readBytes(entry.width * entry.height * 2); // rgba data will go in here entry.rgba = new Uint8ClampedArray(entry.width * entry.height * 4); } // Parse the data for (i = 0; i < this.image_count; i++) { entry = images[i]; for (j = 0; j < entry.buffer.length; j += 2) { number = entry.buffer.readUInt16LE(j); index = (j / 2)*4; // Completely 0 numbers are transparent // Green numbers rgb(50, 255, 67) are also transparent if (!number || number == 2016) { entry.rgba[index+3] = 0; continue; } if (this.format == 0) { // 555 // Red entry.rgba[index] = (number & 0x7c00) >> 7; // Green entry.rgba[index+1] = (number & 0x03e0) >> 2; // Blue entry.rgba[index+2] = (number & 0x001f) << 3; // Alpha (255 is fully visible) entry.rgba[index+3] = 255; } else { // 565 // Red entry.rgba[index] = (number & 0xf800) >> 8; // Green entry.rgba[index+1] = (number & 0x07e0) >> 3; // Blue entry.rgba[index+2] = (number & 0x001f) << 3; // Alpha (255 is fully visible) entry.rgba[index+3] = 255; } if (this.pigment_info) { entry.rgba[index] = ~~(entry.rgba[index ] * this.pigment_info.r); entry.rgba[index+1] = ~~(entry.rgba[index+1] * this.pigment_info.g); entry.rgba[index+2] = ~~(entry.rgba[index+2] * this.pigment_info.b); } if (this.pigment_bleed_info) { // Get the HSL values (especially the hue) hsl = S16.rgbToHsl(entry.rgba[index], entry.rgba[index+1], entry.rgba[index+2]); // Create a new array with the original r, g & b values r = [entry.rgba[index]]; g = [entry.rgba[index+1]]; b = [entry.rgba[index+2]]; // Now iterate over all the bleed genes for (k = 0; k < this.pigment_bleed_info.length; k++) { info = this.pigment_bleed_info[k]; swap = info.swap_coef; rot = info.rotation_coef; hue = hsl[0] - rot; if (hue < 0) { hue += 1; } else if (hue > 1) { hue -= 1; } // Convert this rotated hue back to rgb values temp = S16.hslToRgb(hue, hsl[1], hsl[2]); // Get the rotated red & blue values sr = temp[0]; sb = temp[2]; // Apply swap temp[0] = (sr + ((sr * (1-swap)) + (sb * swap))) / 2; temp[2] = (sb + ((sb * (1-swap)) + (sr * swap))) / 2; // Push the rotated & swapped values to the array r.push(temp[0]); g.push(temp[1]); b.push(temp[2]); } // Take the reduce mean of all the new values and apply those entry.rgba[index ] = S16.meanReduce(r); entry.rgba[index+1] = S16.meanReduce(g); entry.rgba[index+2] = S16.meanReduce(b); } } } }); module.exports = S16;