Loads of BitmapData updates. More details soon.

This commit is contained in:
photonstorm 2014-04-24 03:49:49 +01:00
parent 0f1e0a3d4e
commit c88fa2bd91
4 changed files with 314 additions and 49 deletions

View file

@ -90,6 +90,7 @@ Version 2.0.4 - "Mos Shirare" - in development
* World.wrap will take a game object and if its x/y coordinates fall outside of the world bounds it will be repositioned on the opposite side, for a wrap-around effect. * World.wrap will take a game object and if its x/y coordinates fall outside of the world bounds it will be repositioned on the opposite side, for a wrap-around effect.
* Group.classType allows you to change the type of object that Group.create or createMultiple makes to your own custom class. * Group.classType allows you to change the type of object that Group.create or createMultiple makes to your own custom class.
* Game.scratch is a single handy BitmapData instance that can be used as a visual scratch-pad, for off-screen bitmap manipulation (and is used as such by BitmapData itself). * Game.scratch is a single handy BitmapData instance that can be used as a visual scratch-pad, for off-screen bitmap manipulation (and is used as such by BitmapData itself).
* Device.support32bit is a new boolean that sets if the context supports 32bit pixel manipulation using array buffer views or not.
### Bug Fixes ### Bug Fixes

View file

@ -61,22 +61,32 @@ Phaser.BitmapData = function (game, key, width, height) {
this.ctx = this.context; this.ctx = this.context;
/** /**
* @property {array} imageData - The canvas image data. * @property {ImageData} imageData - The context image data.
*/ */
this.imageData = this.context.getImageData(0, 0, width, height); this.imageData = this.context.getImageData(0, 0, width, height);
/** /**
* @property {UInt8Clamped} pixels - A reference to the context imageData buffer. * @property {ArrayBuffer} buffer - An ArrayBuffer the same size as the context ImageData.
*/ */
if (this.imageData.data.buffer) if (this.imageData.data.buffer)
{ {
this.pixels = this.imageData.data.buffer; this.buffer = this.imageData.data.buffer;
} }
else else
{ {
this.pixels = this.imageData.data; this.buffer = new ArrayBuffer(this.imageData.data.length);
} }
/**
* @property {Uint8ClampedArray} data - A Uint8ClampedArray view into BitmapData.buffer.
*/
this.data = this.imageData.data;
/**
* @property {Uint32Array} pixels - A Uint32Array view into BitmapData.buffer.
*/
this.pixels = new Uint32Array(this.buffer);
/** /**
* @property {PIXI.BaseTexture} baseTexture - The PIXI.BaseTexture. * @property {PIXI.BaseTexture} baseTexture - The PIXI.BaseTexture.
* @default * @default
@ -101,15 +111,24 @@ Phaser.BitmapData = function (game, key, width, height) {
*/ */
this.type = Phaser.BITMAPDATA; this.type = Phaser.BITMAPDATA;
/**
* @property {boolean} disableTextureUpload - If disableTextureUpload is true this BitmapData will never send its image data to the GPU when its dirty flag is true.
*/
this.disableTextureUpload = false;
/** /**
* @property {boolean} dirty - If dirty this BitmapData will be re-rendered. * @property {boolean} dirty - If dirty this BitmapData will be re-rendered.
*/ */
this.dirty = false; this.dirty = false;
/** /**
* @property {function} cls - Alias for BitmapData.clear. * @property {boolean} littleEndian - True if Little Endian, false if Big Endian.
*/ */
this.littleEndian = this.game.device.littleEndian;
// Aliases
this.cls = this.clear; this.cls = this.clear;
this.update = this.refreshBuffer;
}; };
@ -141,7 +160,14 @@ Phaser.BitmapData.prototype = {
}, },
/** /**
* Clears the BitmapData. * Clears the BitmapData context using a clearRect.
*
* @method Phaser.BitmapData#cls
*/
/**
* Clears the BitmapData context using a clearRect.
*
* @method Phaser.BitmapData#clear * @method Phaser.BitmapData#clear
*/ */
clear: function () { clear: function () {
@ -153,7 +179,27 @@ Phaser.BitmapData.prototype = {
}, },
/** /**
* Resizes the BitmapData. * Fills the BitmapData with the given color.
*
* @method Phaser.BitmapData#fill
* @param {number} r - The red color value, between 0 and 0xFF (255).
* @param {number} g - The green color value, between 0 and 0xFF (255).
* @param {number} b - The blue color value, between 0 and 0xFF (255).
* @param {number} [a=255] - The alpha color value, between 0 and 0xFF (255).
*/
fill: function (r, g, b, a) {
if (typeof a === 'undefined') { a = 255; }
this.context.fillStyle = 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
this.context.fillRect(0, 0, this.width, this.height);
this.dirty = true;
},
/**
* Resizes the BitmapData. This changes the size of the underlying canvas and refreshes the buffer.
*
* @method Phaser.BitmapData#resize * @method Phaser.BitmapData#resize
*/ */
resize: function (width, height) { resize: function (width, height) {
@ -166,7 +212,7 @@ Phaser.BitmapData.prototype = {
this.canvas.height = height; this.canvas.height = height;
this.textureFrame.width = width; this.textureFrame.width = width;
this.textureFrame.height = height; this.textureFrame.height = height;
this.imageData = this.context.getImageData(0, 0, width, height); this.refreshBuffer();
} }
this.dirty = true; this.dirty = true;
@ -174,15 +220,48 @@ Phaser.BitmapData.prototype = {
}, },
/** /**
* @method Phaser.BitmapData#refreshBuffer * This re-creates the BitmapData.imageData from the current context.
* It then re-builds the ArrayBuffer, the data Uint8ClampedArray reference and the pixels Uint32Array.
* If not given the dimensions defaults to the full size of the context.
*
* @method Phaser.BitmapData#update
* @param {number} [x=0] - The x coordinate of the top-left of the image data area to grab from.
* @param {number} [y=0] - The y coordinate of the top-left of the image data area to grab from.
* @param {number} [width] - The width of the image data area.
* @param {number} [height] - The height of the image data area.
*/ */
refreshBuffer: function () {
this.imageData = this.context.getImageData(0, 0, this.width, this.height); /**
this.pixels = new Int32Array(this.imageData.data.buffer); * This re-creates the BitmapData.imageData from the current context.
* It then re-builds the ArrayBuffer, the data Uint8ClampedArray reference and the pixels Uint32Array.
* If not given the dimensions defaults to the full size of the context.
*
* @method Phaser.BitmapData#refreshBuffer
* @param {number} [x=0] - The x coordinate of the top-left of the image data area to grab from.
* @param {number} [y=0] - The y coordinate of the top-left of the image data area to grab from.
* @param {number} [width] - The width of the image data area.
* @param {number} [height] - The height of the image data area.
*/
refreshBuffer: function (x, y, width, height) {
// this.data8 = new Uint8ClampedArray(this.imageData.buffer); if (typeof x === 'undefined') { x = 0; }
// this.data32 = new Uint32Array(this.imageData.buffer); if (typeof y === 'undefined') { y = 0; }
if (typeof width === 'undefined') { width = this.width; }
if (typeof height === 'undefined') { height = this.height; }
this.imageData = this.context.getImageData(x, y, width, height);
if (this.imageData.data.buffer)
{
this.buffer = this.imageData.data.buffer;
}
else
{
this.buffer = new ArrayBuffer(this.imageData.data.length);
}
this.data = this.imageData.data;
this.pixels = new Uint32Array(this.buffer);
}, },
@ -199,35 +278,25 @@ Phaser.BitmapData.prototype = {
*/ */
replaceRGB: function (sourceR, sourceG, sourceB, sourceA, destR, destG, destB, destA, region) { replaceRGB: function (sourceR, sourceG, sourceB, sourceA, destR, destG, destB, destA, region) {
var x = 0; var tx = 0;
var y = 0; var ty = 0;
var w = this.width; var w = this.width;
var h = this.height; var h = this.height;
if (region instanceof Phaser.Rectangle) if (region instanceof Phaser.Rectangle)
{ {
x = region.x; tx = region.x;
y = region.y; ty = region.y;
w = region.width; w = region.width;
h = region.height; h = region.height;
} }
// for (var x = tx; x < w)
if (x >= 0 && x <= this.width && y >= 0 && y <= this.height) if (x >= 0 && x <= this.width && y >= 0 && y <= this.height)
{ {
this.pixels[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red; this.pixels[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red;
/*
if (this.isLittleEndian)
{
this.data32[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red;
}
else
{
this.data32[y * this.width + x] = (red << 24) | (green << 16) | (blue << 8) | alpha;
}
*/
// this.imageData.data.set(this.data8); // this.imageData.data.set(this.data8);
@ -253,20 +322,14 @@ Phaser.BitmapData.prototype = {
if (x >= 0 && x <= this.width && y >= 0 && y <= this.height) if (x >= 0 && x <= this.width && y >= 0 && y <= this.height)
{ {
this.pixels[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red; if (this.littleEndian)
/*
if (this.isLittleEndian)
{ {
this.data32[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red; this.pixels[y * this.width + x] = (alpha << 24) | (blue << 16) | (green << 8) | red;
} }
else else
{ {
this.data32[y * this.width + x] = (red << 24) | (green << 16) | (blue << 8) | alpha; this.pixels[y * this.width + x] = (red << 24) | (green << 16) | (blue << 8) | alpha;
} }
*/
// this.imageData.data.set(this.data8);
this.context.putImageData(this.imageData, 0, 0); this.context.putImageData(this.imageData, 0, 0);
@ -293,22 +356,35 @@ Phaser.BitmapData.prototype = {
/** /**
* Get the color of a specific pixel. * Get the color of a specific pixel.
* Note that on little-endian systems the format is 0xAABBGGRR and on big-endian the format is 0xRRGGBBAA.
* *
* @param {number} x - The X coordinate of the pixel to get. * @param {number} x - The X coordinate of the pixel to get.
* @param {number} y - The Y coordinate of the pixel to get. * @param {number} y - The Y coordinate of the pixel to get.
* @return {number} A native color value integer (format: 0xRRGGBB) * @return {number} A native color value integer (format: 0xRRGGBB)
*/ */
getPixel: function (x, y) { getPixel: function (x, y, out) {
if (x >= 0 && x <= this.width && y >= 0 && y <= this.height) var index = ~~(x + (y * this.width));
index *= 4;
if (!out)
{ {
return this.data32[y * this.width + x]; out = { r:0, g:0, b:0, a:0 };
} }
out.r = this.data[index];
out.g = this.data[++index];
out.b = this.data[++index];
out.a = this.data[++index];
return out;
}, },
/** /**
* Get the color of a specific pixel including its alpha value. * Get the color of a specific pixel including its alpha value.
* Note that on little-endian systems the format is 0xAABBGGRR and on big-endian the format is 0xRRGGBBAA.
* *
* @method Phaser.BitmapData#getPixel32 * @method Phaser.BitmapData#getPixel32
* @param {number} x - The X coordinate of the pixel to get. * @param {number} x - The X coordinate of the pixel to get.
@ -319,7 +395,24 @@ Phaser.BitmapData.prototype = {
if (x >= 0 && x <= this.width && y >= 0 && y <= this.height) if (x >= 0 && x <= this.width && y >= 0 && y <= this.height)
{ {
return this.data32[y * this.width + x]; return this.pixels[y * this.width + x];
}
},
/**
* Get the color of a specific pixel including its alpha value as a color object containing r,g,b,a and rgba properties.
*
* @method Phaser.BitmapData#getPixelRGB
* @param {number} x - The X coordinate of the pixel to get.
* @param {number} y - The Y coordinate of the pixel to get.
* @return {object} The color object containing r, g, b, a and rgba properties.
*/
getPixelRGB: function (x, y) {
if (x >= 0 && x <= this.width && y >= 0 && y <= this.height)
{
return this.unpackPixel(this.pixels[y * this.width + x]);
} }
}, },
@ -329,7 +422,7 @@ Phaser.BitmapData.prototype = {
* *
* @method Phaser.BitmapData#getPixels * @method Phaser.BitmapData#getPixels
* @param {Phaser.Rectangle} rect - The Rectangle region to get. * @param {Phaser.Rectangle} rect - The Rectangle region to get.
* @return {array} CanvasPixelArray. * @return {ImageData} Returns a ImageData object containing a Uint8ClampedArray data property.
*/ */
getPixels: function (rect) { getPixels: function (rect) {
@ -337,6 +430,144 @@ Phaser.BitmapData.prototype = {
}, },
/**
* Packs the r, g, b, a components into a single integer, for use with Int32Array.
* If device is little endian then ABGR order is used. Otherwise RGBA order is used.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.BitmapData#packPixel
* @param {number} r - The red byte, 0-255
* @param {number} g - The green byte, 0-255
* @param {number} b - The blue byte, 0-255
* @param {number} a - The alpha byte, 0-255
* @return {number} The packed color
*/
packPixel: function (r, g, b, a) {
if (this.littleEndian)
{
return (a << 24) | (b << 16) | (g << 8) | r;
}
else
{
return (r << 24) | (g << 16) | (b << 8) | a;
}
},
/**
* Unpacks the r, g, b, a components into the specified color object, or a new
* object, for use with Int32Array. If little endian, then ABGR order is used when
* unpacking, otherwise, RGBA order is used. The resulting color object has the
* `r, g, b, a` properties which are unrelated to endianness.
*
* Note that the integer is assumed to be packed in the correct endianness. On little-endian
* the format is 0xAABBGGRR and on big-endian the format is 0xRRGGBBAA. If you want a
* endian-independent method, use fromRGBA(rgba) and toRGBA(r, g, b, a).
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.BitmapData#unpackPixel
* @param {number} rgba - The integer, packed in endian order by packPixel.
* @param {object} out - The color object with `r, g, b, a` properties, or null.
* @return {object} A color representing the pixel at that location.
*/
unpackPixel: function (rgba, out) {
if (!out)
{
out = { r: 0, g: 0, b: 0, a: 0, rgba: '' };
}
if (this.littleEndian)
{
out.a = ((rgba & 0xff000000) >>> 24);
out.b = ((rgba & 0x00ff0000) >>> 16);
out.g = ((rgba & 0x0000ff00) >>> 8);
out.r = ((rgba & 0x000000ff));
}
else
{
out.r = ((rgba & 0xff000000) >>> 24);
out.g = ((rgba & 0x00ff0000) >>> 16);
out.b = ((rgba & 0x0000ff00) >>> 8);
out.a = ((rgba & 0x000000ff));
}
out.rgba = 'rgba(' + out.r + ',' + out.g + ',' + out.b + ',' + out.a + ')';
return out;
},
/**
* A utility to convert an integer in 0xRRGGBBAA format to a color object.
* This does not rely on endianness.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.BitmapData#fromRGBA
* @param {number} rgba - An RGBA hex
* @param {object} [out] - The object to use, optional.
* @return {object} A color object.
*/
fromRGBA: function (rgba, out) {
if (!out)
{
out = { r: 0, g: 0, b: 0, a: 0, rgba: '' };
}
out.r = ((rgba & 0xff000000) >>> 24);
out.g = ((rgba & 0x00ff0000) >>> 16);
out.b = ((rgba & 0x0000ff00) >>> 8);
out.a = ((rgba & 0x000000ff));
out.rgba = 'rgba(' + out.r + ',' + out.g + ',' + out.b + ',' + out.a + ')';
return out;
},
/**
* A utility to convert RGBA components to a 32 bit integer in RRGGBBAA format.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.BitmapData#toRGBA
* @param {number} r - The r color component (0 - 255)
* @param {number} g - The g color component (0 - 255)
* @param {number} b - The b color component (0 - 255)
* @param {number} a - The a color component (0 - 255)
* @return {number} A RGBA-packed 32 bit integer
*/
toRGBA: function (r, g, b, a) {
return (r << 24) | (g << 16) | (b << 8) | a;
},
/**
* A utility function to create a lightweight 'color' object with the default components.
* Any components that are not specified will default to zero.
*
* This is useful when you want to use a shared color object for the getPixel and getPixelAt methods.
*
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.BitmapData#createColor
* @param {number} [r=0] - The r color component (0 - 255)
* @param {number} [g=0] - The g color component (0 - 255)
* @param {number} [b=0] - The b color component (0 - 255)
* @param {number} [a=0] - The a color component (0 - 255)
* @return {object} The resulting color object, with r, g, b, a properties
*/
createColor: function (r, g, b, a) {
var out = { r: r||0, g: g||0, b: b||0, a: a||0 };
out.rgba = 'rgba(' + out.r + ',' + out.g + ',' + out.b + ',' + out.a + ')';
return out;
},
/** /**
* Copies the pixels from the source image to this BitmapData based on the given area and destination. * Copies the pixels from the source image to this BitmapData based on the given area and destination.
* *
@ -460,12 +691,13 @@ Phaser.BitmapData.prototype = {
/** /**
* If the game is running in WebGL this will push the texture up to the GPU if it's dirty. * If the game is running in WebGL this will push the texture up to the GPU if it's dirty.
* This is called automatically if the BitmapData is being used by a Sprite, otherwise you need to remember to call it in your render function. * This is called automatically if the BitmapData is being used by a Sprite, otherwise you need to remember to call it in your render function.
* If you wish to suppress this functionality set BitmapData.disableTextureUpload to `true`.
* *
* @method Phaser.BitmapData#render * @method Phaser.BitmapData#render
*/ */
render: function () { render: function () {
if (this.game.renderType === Phaser.WEBGL && this.dirty) if (!this.disableTextureUpload && this.game.renderType === Phaser.WEBGL && this.dirty)
{ {
// Only needed if running in WebGL, otherwise this array will never get cleared down // Only needed if running in WebGL, otherwise this array will never get cleared down
// should use the rendersession // should use the rendersession

View file

@ -341,6 +341,12 @@ Phaser.Device = function (game) {
*/ */
this.littleEndian = false; this.littleEndian = false;
/**
* @property {boolean} support32bit - Does the device context support 32bit pixel manipulation using array buffer views?
* @default
*/
this.support32bit = false;
/** /**
* @property {boolean} fullscreen - Does the browser support the Full Screen API? * @property {boolean} fullscreen - Does the browser support the Full Screen API?
* @default * @default
@ -688,6 +694,8 @@ Phaser.Device.prototype = {
this.littleEndian = this._checkIsLittleEndian(); this.littleEndian = this._checkIsLittleEndian();
} }
this.support32bit = (typeof ArrayBuffer !== "undefined" && typeof Uint8ClampedArray !== "undefined" && typeof Int32Array !== "undefined" && this.littleEndian !== null && this._checkIsUint8ClampedImageData());
navigator.vibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate; navigator.vibrate = navigator.vibrate || navigator.webkitVibrate || navigator.mozVibrate || navigator.msVibrate;
if (navigator.vibrate) if (navigator.vibrate)
@ -731,6 +739,33 @@ Phaser.Device.prototype = {
}, },
/**
* Test to see if ImageData uses CanvasPixelArray or Uint8ClampedArray.
* @author Matt DesLauriers (@mattdesl)
* @method Phaser.Device#_checkIsUint8ClampedImageData
* @private
*/
_checkIsUint8ClampedImageData: function () {
if (typeof Uint8ClampedArray === "undefined")
{
return false;
}
var elem = document.createElement('canvas');
var ctx = elem.getContext('2d');
if (!ctx)
{
return false;
}
var image = ctx.createImageData(1, 1);
return image.data instanceof Uint8ClampedArray;
},
/** /**
* Check whether the host environment support 3D CSS. * Check whether the host environment support 3D CSS.
* @method Phaser.Device#_checkCSS3D * @method Phaser.Device#_checkCSS3D

View file

@ -233,11 +233,8 @@ Phaser.Color = {
if (typeof alpha === "undefined") { alpha = 255; } if (typeof alpha === "undefined") { alpha = 255; }
// Sanity checks // Sanity checks
if (max > 255) { if (max > 255 || min > max)
return Phaser.Color.getColor(255, 255, 255); {
}
if (min > max) {
return Phaser.Color.getColor(255, 255, 255); return Phaser.Color.getColor(255, 255, 255);
} }