Jump to content

cannot apply gray filter to sprite


Yehuda Katz
 Share

Recommended Posts

Hello everyone, is there any special limitation for using filters? I can see from example on Phaser web site that this code should work but in my app it does not work :(

this.game.load.image('logo', 'assets/images/preload_bg.png');
this.game.load.script('gray', 'https://cdn.rawgit.com/photonstorm/phaser/master/v2/filters/Gray.js');

var logo = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'logo');
logo.anchor.setTo(0.5, 0.5);
var gray = this.game.add.filter('Gray');
logo.filters = [gray];

The sprite is added but it's not affected by filter at all... I checked: that Phaser.Filter.Gray exists, gray object is really filer object and logo has it within filters property. What else I can do to find issue?

Thanks in advance

Link to comment
Share on other sites

16 hours ago, 3man7 said:

Are there any errors on console from your app?

That's the most weird, no errors... I even followed @samid737 suggestion and replaced 'Gray' with 'gray' and got error message:

TypeError: c.Filter[a] is not a constructor

Also, do you test with JS5 or JS6? I am thinking may be those filters are not compatible with JS6 and I should rewrite them? Here is full code:

export default class Achievements extends Phaser.State {
  preload() {
    this.game.load.image('logo', 'assets/images/phaser2.png');
    this.game.load.script('gray', 'https://cdn.rawgit.com/photonstorm/phaser/master/v2/filters/Gray.js');
  }

  create() {
    var logo = this.game.add.sprite(this.game.world.centerX, this.game.world.centerY, 'logo');
    logo.anchor.setTo(0.5, 0.5);
    var gray = this.game.add.filter('Gray');
    logo.filters = [gray];
    gray.update()
  }

  update() {
  }
}

As you can see I even tried to use original phaser2.png file, to be sure that my png file is not not an issue

Thanks and waiting for your suggestions 

Link to comment
Share on other sites

I found the reason, the problem is here:

game = new Phaser.Game(480, 800, Phaser.CANVAS, 'game');

The problem is that I cannot replace Phaser.CANVAS with Phaser.AUTO because it causes blinking when game is run on Android

Any suggestion how to fix that or why filter is not working with CANVAS?

p.s. Just in case if anyone needs Gray filter rewritten into JS6 which can be included into project:

export default class FilterGray extends Phaser.Filter {
  constructor(game) {
    super(game);
    this.uniforms.gray = { type: '1f', value: 1.0 };
    this.fragmentSrc = [
      "precision mediump float;",

      "varying vec2       vTextureCoord;",
      "varying vec4       vColor;",
      "uniform sampler2D  uSampler;",
      "uniform float      gray;",

      "void main(void) {",
      "gl_FragColor = texture2D(uSampler, vTextureCoord);",
      "gl_FragColor.rgb = mix(gl_FragColor.rgb, vec3(0.2126 * gl_FragColor.r + 0.7152 * gl_FragColor.g + 0.0722 * gl_FragColor.b), gray);",
      "}"
    ];
  }

  set gray(value) {
    this.uniforms.gray.value = value;
  }

  get gray() {
    return this.uniforms.gray.value;
  }
}

user is as simple as that:

this.filter_gray = new FilterGray(this.game);
icon.filters = [this.filter_gray];

 
Link to comment
Share on other sites

Well, like @samid737 said, the issue is that you are using CANVAS.

In the testing I've used Phaser 2.6.2 and Phaser.AUTO.

I am not entirely sure if there are filters that can be used with CANVAS as they are meant to work with WEBGL only.

 

Refer to:
https://github.com/photonstorm/phaser/issues/2223
http://phaser.io/docs/2.6.2/Phaser.Filter.html

Quote

@rich: Filters are basically Shaders, and shaders are WebGL only I'm afraid. I have updated the docs to reflect this.

Quote

The vast majority of filters (including all of those that ship with Phaser) use fragment shaders, and
therefore only work in WebGL and are not supported by Canvas at all.

 

Link to comment
Share on other sites

@3man7 I decided to give up with using filters as they drop frame rate to 15-20 from 60 on my PC (I can imagine what will happen on tablet or mobile devises). I have only 80 different sprites created from PNG file and moving them around (scrolling) glitches the screen. Does anyone have have any idea how to fix that or simply do one time dirty work in Photoshop and give up with filters?

Link to comment
Share on other sites

5 hours ago, Yehuda Katz said:

@3man7 I decided to give up with using filters as they drop frame rate to 15-20 from 60 on my PC (I can imagine what will happen on tablet or mobile devises). I have only 80 different sprites created from PNG file and moving them around (scrolling) glitches the screen. Does anyone have have any idea how to fix that or simply do one time dirty work in Photoshop and give up with filters?

The filter option will definitely have an impact on the performance. I would personally apply the filter to the batch of images in Photoshop using 'Action'.

Here's a good tutorial: youtube.com/watch?v=lcZp2h5WXdA

Don't forget to backup your original images!

This method only applies if your sprites will remain grayscale the entire project. If you want to transition from color to grayscale and back on color then I would advice against this method and instead try to find a fix for the code.

Sorry that I cannot help you enough with the filter code as I haven't used them before.

Link to comment
Share on other sites

@3man7 Nope, I need only gray scale image to display locked levels on level selection menu. I thought may be Phaser can somehow do the job once, cache that gray scale image and use it with every update... instead of simply doing whole filter job all the time =) Once I will have free time, I will try write some code which will cache that gray scale image of image and share the code here (in case of success)

Link to comment
Share on other sites

17 minutes ago, Yehuda Katz said:

@3man7 Nope, I need only gray scale image to display locked levels on level selection menu. I thought may be Phaser can somehow do the job once, cache that gray scale image and use it with every update... instead of simply doing whole filter job all the time =) Once I will have free time, I will try write some code which will cache that gray scale image of image and share the code here (in case of success)

Ohh I get it.

What I would've done instead is this: create a black box with the transparency of 60% and save it as .png. After that just duplicate the .png image with 'for loop', put them in a group and place them over the colored levels (aka unlocked). Then kill() the image specific to the levels unlocked.

It won't be grayscale but it's a similar effect.

In the end it's up to you :)

Link to comment
Share on other sites

@3man7 thanks, I used similar trick for gradient transparency to hide part of scrolling level map at the top and bottom. I simply created PNG file with gradient transparency and put it over =) I will probably do my own class but with next update =)

@samme what if I will simply do scan line of sprite's context and cache result, will not it be much faster? BTW you asked me to share solution for centering the text, here it is (I just finished and it works awesome):

_text_true_size(object) {
  var cache_key = object.text + JSON.stringify(object.style);
  if ('true_size_cache' in object && object.true_size_cache[cache_key])
    return object.true_size_cache[cache_key];

  var true_size_data = {left: -Infinity, top: -Infinity, right: Infinity, bottom: Infinity, width: Infinity, height: Infinity};
  var width = object.canvas.width, height = object.canvas.height;
  var image_data = object.context.getImageData(0, 0, width, height).data;
  var line_index = 0, canvas_line_width = width * 4, i, j, stop;
  var pixel_color = 0, background_color = image_data[0] + image_data[1] + image_data[2] + image_data[3];

  stop = false;
  for (i=0; i<height; i++) {
    for (j=0; j<canvas_line_width; j+=4) {
      pixel_color = image_data[line_index + j] + image_data[line_index + j + 1] + image_data[line_index + j + 2] + image_data[line_index + j + 3];
      if (pixel_color != background_color) {
        true_size_data.top = i;
        stop = true;
        break;
      }
    }
    if (stop) break;
    line_index += canvas_line_width;
  }

  line_index = image_data.length - canvas_line_width;
  stop = false;
  for (i=height; i>0; i--) {
    for (j=0; j<canvas_line_width; j+=4) {
      pixel_color = image_data[line_index + j] + image_data[line_index + j + 1] + image_data[line_index + j + 2] + image_data[line_index + j + 3];
      if (pixel_color != background_color) {
        true_size_data.bottom = height - i;
        stop = true;
        break;
      }
    }
    if (stop) break;
    line_index -= canvas_line_width;
  }

  stop = false;
  for (i=0; i<canvas_line_width; i+=4) {
    line_index = 0;
    for (j=0; j<height; j++) {
      pixel_color = image_data[line_index + i] + image_data[line_index + i + 1] + image_data[line_index + i + 2] + image_data[line_index + i + 3];
      if (pixel_color != background_color) {
        true_size_data.left = Math.floor(i / 4);
        stop = true;
        break;
      }
      line_index += canvas_line_width;
    }
    if (stop) break;
  }

  stop = false;
  for (i=canvas_line_width; i>3; i-=4) {
    line_index = 0;
    for (j=0; j<height; j++) {
      pixel_color = image_data[line_index + i] + image_data[line_index + i + 1] + image_data[line_index + i + 2] + image_data[line_index + i + 3];
      if (pixel_color != background_color) {
        true_size_data.right = width - Math.floor(i / 4);
        stop = true;
        break;
      }
      line_index += canvas_line_width;
    }
    if (stop) break;
  }

  true_size_data.width = width - true_size_data.left - true_size_data.right;
  true_size_data.height = height - true_size_data.top - true_size_data.bottom;
  object.true_size_cache = {};
  object.true_size_cache[cache_key] = true_size_data;

  return object.true_size_cache[cache_key];
}

It assumes that top left pixel is the background color. Actually in 99% it's transparent but I decided to support rare cases too. It also uses compares sum of color (4 bytes) to speed up the search. The result is cached so next call should be instant.
Edited by Yehuda Katz
there was a bug in calculating right and bottom margin values; cache key should be bases on text + style value
Link to comment
Share on other sites

Oh and here is the sample usage:

var true_size = this._text_true_size(object);
var offset_x = (object.width / 2) - (true_size.left + (true_size.width / 2));
var offset_y = (object.height / 2) - (true_size.top + (true_size.height / 2));
var rect = new Phaser.Rectangle(object.x, object.y, object.bounds.width, object.bounds.height);
object.alignIn(rect, align, offset_x, offset_y);

Currently I am working on a code to reduce font size automatically so it could fit the bounds and in case it cannot fit then it should auto. short. words =)

p.s. The only downside of such method is that you should call alignIn method every time you change the text. I tried to overwrite 'text' setter but without any success. I am not sure how to do that correctly in JS6 (with methods it's simply super.method_name())

Link to comment
Share on other sites

@samid737 what about processPixelRGB method, is it significantly slower than scan line? As far as I now, JS works faster with one dimensional arrays... when I got inside of processPixelRGB method, I can see that it does many calculations and conversions. However, if that's just 10-50msec then I would prefer cleaner (readable) code with processPixelRGB over faster getImageData

Link to comment
Share on other sites

@samme I am not sure if it will work for my particular case (my question was simplified) but I like clipping path, which may come up handy for weird clip masks, I should bookmark that page, thanks!

@samid737 The getImageData() method seems to be native HTML5 method  as it works with canvas context (the text actually has both canvas and context public properties). Probably it would be nice to have small wrapper function to quickly access line and get each pixel as array/object (in win32 development, working with canvas is hundreds slower than bitmap and bitmaps are another hundreds slower than scan line, which is something similar what getImageData() method returns). I just finished auto_fit_bounds method and should have time to give a shot for a custom gray scale converter (I will update topic if I have success)

Link to comment
Share on other sites

  • 2 weeks later...

@samme it took me some time to finish all small tasks from todo and today I tested globalCompositeOperation. It works just awesome (and very fast too):

var icon_bmd_source = new Phaser.BitmapData(this.game, '', 120, 120);
icon_bmd_source.draw(new Phaser.Sprite(this.game, 0, 0, 'default', 'level_icon_1'));
var icon_bmd_overlay = new Phaser.BitmapData(this.game, '', 120, 120);
icon_bmd_overlay.draw(new Phaser.Sprite(this.game, 0, 0, 'default', 'level_disable_overlay'));

var icon_bmd_result = new Phaser.BitmapData(this.game, '', 120, 120);
icon_bmd_result.context.drawImage(icon_bmd_source.canvas, 0, 0);
icon_bmd_result.context.globalCompositeOperation = 'source-in';
icon_bmd_result.context.drawImage(icon_bmd_overlay.canvas, 0, 0);

var sprite = new Phaser.Sprite(this.game, 50, 100, icon_bmd_result);
this.game.add.existing(sprite);

The whole idea is that I created filled rectangle (if needed it can be partially transparent) from which I use only those pixels, which are not transparent in source =) However, I have no clue why I cannot reach canvas or context of Sprite... while I was able to do that with Phaser.Text object.

Link to comment
Share on other sites

So here is the final version which combines any two sprite and returns BitmapData which later can be used to update existing sprite via:

my_sprite.loadTexture(this._combine_sprite(icon_sprite, overlay_sprite, 'source-in'));
_combine_sprite(first_sprite, second_sprite, combine_mode) {
  var first_bmd = new Phaser.BitmapData(this.game, '', first_sprite.width, first_sprite.height);
  first_bmd.draw(first_sprite);
  var second_bmd = new Phaser.BitmapData(this.game, '', second_sprite.width, second_sprite.height);
  second_bmd.draw(second_sprite);

  var result_bmd = new Phaser.BitmapData(this.game, '', first_sprite.width, first_sprite.height);
  result_bmd.context.drawImage(first_bmd.canvas, 0, 0);
  result_bmd.context.globalCompositeOperation = combine_mode;
  result_bmd.context.drawImage(second_bmd.canvas, 0, 0);
  return result_bmd;
}

p.s. I decided to pass overlay sprite because there will be many icons and there is no reason to create overlay sprite every time I need to update icon. Instead I created one overlay sprite and use it many times for all icons I have

Link to comment
Share on other sites

 Share

  • Recently Browsing   0 members

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