Jump to content

BitmapData.setHSL desaturation


kenray
 Share

Recommended Posts

I very much appreciate the new BitmapData.setHSL method in 2.0.4, and it seems great for *saturating* a bitmap by increasing the value above 0 towards 1... but is there a way do *desaturate* a bitmap so that it goes grayscale? Adobe Fireworks (for example) has a range of -100 to 100, where -100 saturation is fully desaturated grayscale. I was hoping that setHSL would support a -1 to 1 range (instead of its current 0 to 1 range), and in fact when I tried some negative values it led to odd results <grin>.

 

What I'm trying to do is to turn a bitmap from its normal saturation to a grayscale version *over time*. Although I can use something like the new processPixelRGB method to iterate over each pixel with a callback, that gets really slow. So I was hoping to be able to do a dozen or so timed executions of adjusting the setHSL's saturation value to fully desaturate a bitmap over a second or two.

 

So I'd like to vote for either a -1 to 1 range for H, S and L or in the meantime, is there some way to efficiently grayscale a bitmap over time?

 

(oh, and it needs to work in both WebGL and Canvas... )

 

Thanks!

Link to comment
Share on other sites

Actually, I discovered that while bmd.shiftHSL works great to switch to grayscale immediately, the problem with desaturating over time is that when bmd.shiftHSL is run, it permanently alters the original bitmap data. What I was hoping to do was to (for example) loop 10 times through from 0 to -1, stepping by -0.1 to gradually desaturate it (so the saturation level would go 90%, 80%, 70%, etc.), but since it permanently alters the bitmap data, each loop accelerates the desaturation (90%, 72%, 50%, 30%, etc.), and of course once its fully desaturated, it can't re-saturate over time.

 

So what I'm trying to accomplish is a sprite that's clicked desaturates to grayscale in like 2000ms, and when clicked again, it re-saturates back to its original level in 2000ms.

 

Any ideas on how to accomplish this?

Link to comment
Share on other sites

I just tried that and it looks as though it is stacking multiple copies of the image on top of itself each way through the loop - I used an example of a large version of the Adobe PDF document icon (because it has antialiasing around the edges), and here's what I get:

 

Original:

Voila_Capture%202014-05-09_07-09-40_AM.p

 

After running loop:

Voila_Capture%202014-05-09_07-10-28_AM.p

However seeing that, I put in a bmd.cls() before the bmd.draw() and it worked!

 

So for others who might be interested, here's the code that worked to desaturate and resaturate a sprite over time (2000ms each way):

// Requires Phaser 2.0.4+// Assumes you've preloaded a 100x200 image with the key 'MyImage' in the cache// and created the sprite called 'MySprite' at location 500,500; to make it easier// I added a property to MySprite that held a reference to the bitmapData object, // another to hold the current saturation value, and a third to hold the name of // the sprite itself for later use// Preload phase:game.load.image('MyImage','path/to/myimage.png');// Create phase:var bmd = game.add.bitmapData(100, 200);var Rect = new Phaser.Rectangle(0, 0, 100, 200);bmd.copyPixels('MyImage', Rect, 0, 0);bmd.update();MySprite = game.add.sprite(500, 500, bmd);MySprite._bitmap = bmd;MySprite._name = 'MyImage';MySprite._saturation = 0;// Global functions:function Desaturate(pSprite, pDuration) {  var tMSPerStep = 100;  // I didn't want any step to take longer than 100ms  if (pSprite._saturation > -1.0) {    if (pDuration <= 100) {        pSprite._bitmap.shiftHSL(null, -1.0, null);        pSprite._saturation = -1.0;    } else {      var tNumSteps = Math.floor(pDuration/tMSPerStep) + 1;      var tCurrStep = 1;      var tMethod = "desaturate";      ChangeSaturation(pSprite, tMethod, tCurrStep, tNumSteps, tMSPerStep);    }  }}function Resaturate(pSprite, pDuration) {  var tMSPerStep = 100;  // I didn't want any step to take longer than 100ms  if (pSprite._saturation <= -1.0) {    if (pDuration <= 100) {      pSprite._bitmap.copyPixels(pSprite.draw(pSprite._name, 0, 0);      pSprite._saturation = 0;    } else {      var tNumSteps = Math.floor(pDuration/tMSPerStep) + 1;      var tCurrStep = 1;      var tMethod = "resaturate";      ChangeSaturation(pSprite, tMethod, tCurrStep, tNumSteps, tMSPerStep);    }  }}function ChangeSaturation(pSprite, pMethod, pCurrStep, pNumSteps, pMSPerStep) {  var tSaturation;  var tBaseSaturation = (pCurrStep / pNumSteps);  if (pCurrStep <= pNumSteps) {    if (pMethod == "desaturate") {      tSaturation = -1 * tBaseSaturation;    } else {      tSaturation = -1 * (1 - tBaseSaturation);    }    pSprite._bitmap.cls();    pSprite._bitmap.draw(pSprite._name, 0, 0);    pSprite._bitmap.update();    pSprite._bitmap.shiftHSL(null, tSaturation, null);    pSprite._saturation = tSaturation;    pCurrStep += 1;    game.time.events.add(pMSPerStep, ChangeSaturation, this, pSprite, pMethod, pCurrStep, pNumSteps, pMSPerStep);  }}// Use:Desaturate(MySprite, 2000);Resaturate(MySprite, 2000);

Thanks for all your help, Rich!

Link to comment
Share on other sites

I *thought* it didn't use WebGL (which is why I liked it in the first place!).

 

Do you have any idea why Safari's not rendering the image at http://www.sonsothunder.com/test/grayscale/grayscaleTest.html in grayscale? If you go to that page in Chrome, it's grayscale, but in Safari it's not.

 

Here's the complete JS for the page:

var Phaser,game;var FillBMD;var tWidth = 209;var tHeight = 255;var tName = 'arnold';game = new Phaser.Game(800, 600, Phaser.CANVAS, '');var MainState = function(game){	this.game = game;};MainState.prototype = {	preload: function() {		game.load.image('arnold', 'assets/arnold-rest.png');	},	create: function() {		FillBMD = game.add.bitmapData(tWidth, tHeight);		var Rect = new Phaser.Rectangle(0, 0, tWidth, tHeight);		FillBMD.copyPixels(tName, Rect, 0, 0);		FillBMD.update();		FillBMD.shiftHSL(null, -1.0, null);		var tFillSprite = game.add.sprite(200, 200, FillBMD);	},	update: function() {	},	render: function() {	}}game.state.add('mainstate', MainState);game.state.start('mainstate');

BTW, I tried Phaser.AUTO too and got the same thing...

Link to comment
Share on other sites

Thanks to the browser shots (thanks, jpdev!) and a bit of research, it looks like it's WebKit-related. Luakit, Rekonq, and Safari (at least) all render my test in color, but Chrome, Firefox, etc. render it grayscale... So now that it looks like the why is answered, the remaining question for you, Rich, is: is there anything that can be done about it? Or is it something that is just "the way it is"?

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...