graphics/webimage.js

'use strict';

var Thing = require('./thing.js');

var UNDEFINED = -1;
var NOT_LOADED = 1;
var DONE = 2;

var NUM_CHANNELS = 4;
var RED = 0;
var GREEN = 1;
var BLUE = 2;
var ALPHA = 3;

/**
 * @constructor
 * @augments Thing
 * @param {string} filename - Filepath to the image
 */
function WebImage(filename) {
    if (typeof filename !== 'string') {
        throw new TypeError(
            'You must pass a string to <span class="code">' +
                "new WebImage(filename)</span> that has the image's location."
        );
    }
    Thing.call(this);
    var self = this;

    this.image = new Image();
    this.image.src = filename;
    this.filename = filename;
    this.width = NOT_LOADED;
    this.height = NOT_LOADED;
    this.image.onload = function() {
        self.checkDimensions();
        if (self.loadfn) {
            self.loadfn();
        }
    };
    this.set = 0;
    this.type = 'WebImage';

    this.displayFromData = false;
    this.isCrossOrigin = false;
    this.data = NOT_LOADED;
}

WebImage.prototype = new Thing();
WebImage.prototype.constructor = WebImage;

/**
 * Set a function to be called when the WebImage is loaded.
 *
 * @param {function} callback - A function
 */
WebImage.prototype.loaded = function(callback) {
    this.loadfn = callback;
};

/**
 * Set the image of the WebImage.
 *
 * @param {string} filename - Filepath to the image
 */
WebImage.prototype.setImage = function(filename) {
    var self = this;

    this.image = new Image();
    this.image.src = filename;
    this.filename = filename;
    this.width = NOT_LOADED;
    this.height = NOT_LOADED;
    this.image.onload = function() {
        self.checkDimensions();
        if (self.loadfn) {
            self.loadfn();
        }
    };
    this.set = 0;

    this.displayFromData = false;
    this.isCrossOrigin = false;
    this.data = NOT_LOADED;
};

/**
 * Reinforce the dimensions of the WebImage based on the image it displays.
 */
WebImage.prototype.checkDimensions = function() {
    if (this.width == NOT_LOADED) {
        this.width = this.image.width;
        this.height = this.image.height;
    }
};

/**
 * Draws the WebImage in the canvas.
 *
 * @param {CodeHSGraphics} __graphics__ - Instance of the __graphics__ module.
 */
WebImage.prototype.draw = function(__graphics__) {
    this.checkDimensions();
    var context = __graphics__.getContext('2d');
    // http://stackoverflow.com/questions/17125632/html5-canvas-rotate-object-without-moving-coordinates
    context.save();
    context.beginPath();

    context.translate(this.x + this.width / 2, this.y + this.height / 2);
    context.rotate(this.rotation);

    // If we should be displaying the underlying pixel data, display that
    // Otherwise display the image
    if (this.displayFromData && this.data !== NOT_LOADED) {
        // putImageData does not respect context transformations so use
        // this.x and this.y for the upper left corner
        context.putImageData(this.data, this.x, this.y);
    } else {
        context.drawImage(this.image, -this.width / 2, -this.height / 2, this.width, this.height);
    }

    // If we haven't updated the underlying pixel data yet, try to get
    // the pixel data from the canvas context.
    try {
        if (this.data === NOT_LOADED && !this.isCrossOrigin) {
            // get the ImageData for this image
            this.data = context.getImageData(this.x, this.y, this.width, this.height);
        }
    } catch (err) {
        // Image was cross origin.
        // Fail silently so we can still display images from cross origin,
        // just not access cross origin image data
        this.data = NOT_LOADED;
        this.isCrossOrigin = true;
    }

    context.closePath();
    context.restore();
};

/**
 * Assign the ImageData of the canvas at the position of the image,
 * essentially assigning the ImageData.
 * Read more at https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/getImageData
 */
WebImage.prototype.getData = function(__graphics__) {
    var context = __graphics__.getContext();
    //context.drawImage(this.image, this.x, this.y, this.width, this.height);
    this.data = context.getImageData(this.x, this.y, this.width, this.height);
};

/**
 * Checks if the passed point is contained in the WebImage.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {boolean} Whether the passed point is contained in the WebImage.
 */
WebImage.prototype.containsPoint = function(x, y) {
    return x >= this.x && x <= this.x + this.width && y >= this.y && y <= this.y + this.height;
};

/**
 * Gets the width of the WebImage.
 *
 * @returns {number} Width of the WebImage.
 */
WebImage.prototype.getWidth = function() {
    return this.width;
};

/**
 * Gets the height of the WebImage.
 *
 * @returns {number} Height of the WebImage.
 */
WebImage.prototype.getHeight = function() {
    return this.height;
};

/**
 * Sets the size of the WebImage.
 *
 * @param {number} width - The desired width of the resulting WebImage.
 * @param {number} height - The desired height of the resulting WebImage.
 */
WebImage.prototype.setSize = function(width, height) {
    this.width = width;
    this.height = height;
};

/* Get and set pixel functions */

/**
 * Gets a pixel at the given x and y coordinates.
 * Read more here:
 * https://developer.mozilla.org/en-US/docs/Web/API/ImageData/data
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {array} An array of 4 numbers representing the (r,g,b,a) values
 *                     of the pixel at that coordinate.
 */
WebImage.prototype.getPixel = function(x, y) {
    if (this.data === NOT_LOADED || x > this.width || x < 0 || y > this.height || y < 0) {
        var noPixel = [UNDEFINED, UNDEFINED, UNDEFINED, UNDEFINED];
        return noPixel;
    } else {
        var index = NUM_CHANNELS * (y * this.width + x);
        var pixel = [
            this.data.data[index + RED],
            this.data.data[index + GREEN],
            this.data.data[index + BLUE],
            this.data.data[index + ALPHA],
        ];
        return pixel;
    }
};

/**
 * Get the red value at a given location in the image.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {integer} An integer between 0 and 255.
 */
WebImage.prototype.getRed = function(x, y) {
    return this.getPixel(x, y)[RED];
};

/**
 * Get the green value at a given location in the image.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {integer} An integer between 0 and 255.
 */
WebImage.prototype.getGreen = function(x, y) {
    return this.getPixel(x, y)[GREEN];
};

/**
 * Get the blue value at a given location in the image.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {integer} An integer between 0 and 255.
 */
WebImage.prototype.getBlue = function(x, y) {
    return this.getPixel(x, y)[BLUE];
};

/**
 * Get the alpha value at a given location in the image.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @returns {integer} An integer between 0 and 255.
 */
WebImage.prototype.getAlpha = function(x, y) {
    return this.getPixel(x, y)[ALPHA];
};

/**
 * Set the `component` value at a given location in the image to `val`.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @param {integer} component - Integer representing the color value to
 * be set. R, G, B = 0, 1, 2, respectively.
 * @param {integer} val - The desired value of the `component` at the pixel.
 * Must be between 0 and 255.
 */
WebImage.prototype.setPixel = function(x, y, component, val) {
    if (this.data !== NOT_LOADED && !(x < 0 || y < 0 || x > this.width || y > this.height)) {
        // Update the pixel value
        var index = NUM_CHANNELS * (y * this.width + x);
        this.data.data[index + component] = val;

        // Now that we have modified the image data, we need to display
        // the image based on the underlying image data rather than the
        // image url
        this.displayFromData = true;
    }
};

/**
 * Set the red value at a given location in the image to `val`.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @param {integer} val - The desired value of the red component at the pixel.
 * Must be between 0 and 255.
 */
WebImage.prototype.setRed = function(x, y, val) {
    this.setPixel(x, y, RED, val);
};

/**
 * Set the green value at a given location in the image to `val`.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @param {integer} val - The desired value of the green component at the pixel.
 * Must be between 0 and 255.
 */
WebImage.prototype.setGreen = function(x, y, val) {
    this.setPixel(x, y, GREEN, val);
};

/**
 * Set the blue value at a given location in the image to `val`.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @param {integer} val - The desired value of the blue component at the pixel.
 * Must be between 0 and 255.
 */
WebImage.prototype.setBlue = function(x, y, val) {
    this.setPixel(x, y, BLUE, val);
};

/**
 * Set the alpha value at a given location in the image to `val`.
 *
 * @param {number} x - The x coordinate of the point being tested.
 * @param {number} y - The y coordinate of the point being tested.
 * @param {integer} val - The desired value of the alpha component at the
 * pixel.
 * Must be between 0 and 255.
 */
WebImage.prototype.setAlpha = function(x, y, val) {
    this.setPixel(x, y, ALPHA, val);
};

module.exports = WebImage;