InsOp

Outline a Sprite / change certain colors

Recommended Posts

Hello dear PixiJS community,

finally i began to love pixijs :)

now I want to 

a ) outline a sprite (from a png file as a texture)

like ive done it here with canvas:

https://twitter.com/InsOp_de/status/514917023839944704

b ) change a certain color into another one (usually magenta or pink)

again, like in my example the blue square.

that way i dont have to make a sprite for every color, but use only one and

manipulate the image data.

 

With canvas i did it like that

// getting Stuff        var imgData = game.canvasForegroundContext.getImageData(x, y, imageSize, imageSize);        var data = imgData.data;        //height of the image section        var row = imgData.data.length/imageSize;        var sur = {            nw:-row-4,n:-row,no:-row+4,            w:-4,   		 o:+4,            sw:+row-4,s:+row,so:+row+4            };        for (var i = 0; i < data.length; i += 4) {            //surroundings            if(data[i+3]!=255){                for(var key in sur){                    j = i+sur[key]                    //ignore shadows, check if isnt already an outline                    if(data[j+3]>100                             && data[j] != rgba.r                            && data[j + 1] != rgba.g                            && data[j + 2] != rgba.{                                                data[i] = rgba.r;                        data[i + 1] = rgba.g;                        data[i + 2] = rgba.b;                        data[i + 3] = rgba.a;                    }                }            }                   }

and this was pretty ugly. is there already a filter or a function for this? or perhaps is there something like "imgData.data;" ?

Share this post


Link to post
Share on other sites

Yes, I found that link too. Actually got a step by step lesson on how to do this in my IRC logs. If you want, I can PM you the whole log of me talking with another guy in #webgl on Freenode about making a border filter. I didn't bother with trying to go much further with it, because I got my game fast enough to not need this.

 

"How to manipulate webgl data in a sprite?"

 

You don't. You manipulate the texture, and then you make a sprite from that texture using (IIRC) .fromCanvas...

Share this post


Link to post
Share on other sites

Edit: I found this fiddle: http://jsfiddle.net/Eskel/g593q/9/

but no Idea how to implement that with pixijs

this will not outline picture drawn on your texture, but only rectangle on which is texture mapped. and in webgl only

 

what you can do is to have two textures :

- 1st will be your original texture

- 2nd will be modified texture like you already did with your code ( draw orinigal texture to hidden canvas, do the magic stuff, get new texture using PIXI.Texture.fromCanvas)

 

and then switch them accordingly:

 

mySprite.mouseover = function() {

  this.setTexture(outlinedTexture);

}

 

mySprite.mouseout = function() {

  this.setTexture(originalTexture);

}

 

and this will be working with both - canvas & webgl renderer

Share this post


Link to post
Share on other sites

bubamara, that is a fine strategy if you have a very small amount of textures. But if the game is texture heavy, your technique doubles texture use.

 

In my jigsaw game, I recreate each piece from the base image and cut based on a set of bezier curves. Then I apply the outline, or not, and create/destroy the texture as needed.

 

One con of my approach is that for real-time outline color fading/switching effects, this may be a little on the slow side. I do have such effects but I mitigate the problem by only changing colors every 6 frames or so, instead of every frame.

Share this post


Link to post
Share on other sites

 

b ) change a certain color into another one (usually magenta or pink)

 

ColorMatrixFilter - http://www.html5gamedevs.com/topic/9679-colormatrixfilter-documentation/ 

 

 

a ) outline a sprite (from a png file as a texture)

like ive done it here with canvas:

https://twitter.com/InsOp_de/status/514917023839944704

 

 

You can create a pink version of your sprite using ColorMatrixFilter, scale it up slightly, and put it under the real sprite. But this "outline" will not be very accurate. Might work for simple shapes...

Share this post


Link to post
Share on other sites

Then you need a custom shader. I've never written a shader myself but looking at the source code of ConvolutionFilter it does not seem too hard to modify it to create outlines.

Share this post


Link to post
Share on other sites

Thank you for your answers!

@msha

a) i think your solution is what i needed, just implemented it in my game and had a performance issue, since i had a lot of sprites with all those filters on them so i tweaked your

solution a little bit: http://codepen.io/InsOp/pen/EawxbO

(created a temporary renderer in which I copy the sprite via toDataURL and give that back instead of the sprite with the many filters)

 

b ) now i understand color matrices, thank you! but it didnt solved my issue which is to replace a certain color like #ff00d2 to another.

With those color matrices i am only able to change colordues, for example all red dues, instead of a certain red shade which is what i need

CEKBHnF.png

Share this post


Link to post
Share on other sites
 

Thank you for your answers!

@msha

a) i think your solution is what i needed, just implemented it in my game and had a performance issue, since i had a lot of sprites with all those filters on them so i tweaked your

solution a little bit: http://codepen.io/InsOp/pen/EawxbO

(created a temporary renderer in which I copy the sprite via toDataURL and give that back instead of the sprite with the many filters)

I made an outline shader recently, it's faster and creates better antialiased outlines than that version, feel free to use it - http://codepen.io/mishaa/pen/emGNRB

But caching the outline is still a good idea.

 

 

 

b ) now i understand color matrices, thank you! but it didnt solved my issue which is to replace a certain color like #ff00d2 to another.

With those color matrices i am only able to change colordues, for example all red dues, instead of a certain red shade which is what i need

CEKBHnF.png

That's easy: http://codepen.io/mishaa/pen/emGNXE

Share this post


Link to post
Share on other sites

works like a charm! *

consider the problem solved!

even tho i think those should be native filters in pixijs!

 

whats written in fragmentSrc - is this webgl/opengl?

 

* I had trouble with the OutlineFilter - so I used OutlineFilterMultiPass, which is fast enough if I initialize the filter while loading, not while runtime.

anyway those are the results I had with OutlineFilter: (notice two of the small buildings work - not all of them tho)

ZjMOfmf.jpg

Share this post


Link to post
Share on other sites

@InsOp

Yes, that's OpenGL shading language - GLSL.

 

" I had trouble with the OutlineFilter - so I used OutlineFilterMultiPass" 

I guess it was because of the shader property, which is a little limited, for example it only works for sprites, not DOC-s(but it's faster).

 

Also I had a mistake in my example, If you use the shader property, the arguments should be baseTexture width and height: sprite.shader = new OutlineFilter(sprite.texture.baseTexture.width, sprite.texture.baseTexture.height,....); that asymmetry of outlines must be caused by that.

Share this post


Link to post
Share on other sites

Ah I see - have to get known to this!

 

The difference between those who worked, and those who didnt is, that those who work only have one element in their spriteSheet-Array

PIXI.Sprite.fromFrame(item.spriteSheet[ item.direction ]);

so the animated ones are the problem (and even those who arent animated in the moment but have a spriteSheet.length > 1)

this should not interfere with the shader at all, but i cant get up with another explanation - and i didnt used DOCs in the first place

(but I started to use them right now)

so still the same problem: [but im fine - with the other method it works just perfectly fine]

W82599s.jpg

Share this post


Link to post
Share on other sites

House outlines are not perfect on this screenshot, horizontal lines are much thicker than vertical, are you sure you're passing renderer.width and height as arguments to the filter?

Share this post


Link to post
Share on other sites

this works: 

sprite.filters = new OutlineFilterMultiPass(game.renderer.width, game.renderer.height, thickness, "0x"+friendFoeColors[key]);   

 

this doesnt:

sprite.shader = new OutlineFilter(sprite.texture.baseTexture.width, sprite.texture.baseTexture.height,....)

Share this post


Link to post
Share on other sites

How do I use msha's color replacement shader? I've copied and pasted it out of codepen, but I keep getting the following error.. which is a syntax error for a comma in the fragment shader maybe..?

pixi.js:15985 ERROR: 0:1: ',' : syntax error pixi.js:15586 WebGL: INVALID_VALUE: attachShader: no object or object deletedpixi.js:15592 Pixi.js Error: Could not initialize shader.60.Shader.compile @ pixi.js:1559260.Shader.init @ pixi.js:15526Shader @ pixi.js:15514TextureShader @ pixi.js:1605049.AbstractFilter.getShader @ pixi.js:1397767.SpriteRenderer.flush @ pixi.js:1754848.WebGLRenderer.renderDisplayObject @ pixi.js:1365648.WebGLRenderer.render @ pixi.js:13628animate @ main.js:1078pixi.js:15593 gl.VALIDATE_STATUS false60.Shader.compile @ pixi.js:1559360.Shader.init @ pixi.js:15526Shader @ pixi.js:15514TextureShader @ pixi.js:1605049.AbstractFilter.getShader @ pixi.js:1397767.SpriteRenderer.flush @ pixi.js:1754848.WebGLRenderer.renderDisplayObject @ pixi.js:1365648.WebGLRenderer.render @ pixi.js:13628animate @ main.js:1078pixi.js:15594 gl.getError() 128160.Shader.compile @ pixi.js:1559460.Shader.init @ pixi.js:15526Shader @ pixi.js:15514TextureShader @ pixi.js:1605049.AbstractFilter.getShader @ pixi.js:1397767.SpriteRenderer.flush @ pixi.js:1754848.WebGLRenderer.renderDisplayObject @ pixi.js:1365648.WebGLRenderer.render @ pixi.js:13628animate @ main.js:1078pixi.js:15599 Pixi.js Warning: gl.getProgramInfoLog() missing shaders5pixi.js:15542 WebGL: INVALID_VALUE: getUniformLocation: no object or object deleted3pixi.js:15554 WebGL: INVALID_VALUE: getAttribLocation: no object or object deletedpixi.js:17609 WebGL: INVALID_OPERATION: drawElements: no valid shader program in use

The exact shader is:

var ColorReplaceFilter = function (findColor, replaceWithColor, range) {    PIXI.AbstractFilter.call(this);  this.uniforms = {    findColor: {type: '3f', value: null},    replaceWithColor: {type: '3f', value: null},    range: {type: '1f', value: null}  };  this.findColor = findColor;  this.replaceWithColor = replaceWithColor;  this.range = range;  this.passes = [this];  this.fragmentSrc = [    'precision mediump float;',    'varying vec2 vTextureCoord;',    'uniform sampler2D texture;',    'uniform vec3 findColor;',    'uniform vec3 replaceWithColor;',    'uniform float range;',    'void main(void) {',    '  vec4 currentColor = texture2D(texture, vTextureCoord);',    '  vec3 colorDiff = findColor - (currentColor.rgb / max(currentColor.a, 0.0000000001));',    '  float colorDistance = length(colorDiff);',    '  float doReplace = step(colorDistance, range);',    '  gl_FragColor = vec4(mix(currentColor.rgb, (replaceWithColor + colorDiff) * currentColor.a, doReplace), currentColor.a);',    '}'  ]//.join('\n');  console.log('FRAGMENTSHADER', this.fragmentSrc.join('\n'));};ColorReplaceFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);ColorReplaceFilter.prototype.constructor = ColorReplaceFilter;Object.defineProperty(ColorReplaceFilter.prototype, 'findColor', {  set: function (value) {    var r = ((value & 0xFF0000) >> 16) / 255,        g = ((value & 0x00FF00) >> 8) / 255,        b = (value & 0x0000FF) / 255;    this.uniforms.findColor.value = {x: r, y: g, z: b};    this.dirty = true;  }});Object.defineProperty(ColorReplaceFilter.prototype, 'replaceWithColor', {  set: function (value) {    var r = ((value & 0xFF0000) >> 16) / 255,        g = ((value & 0x00FF00) >> 8) / 255,        b = (value & 0x0000FF) / 255;    this.uniforms.replaceWithColor.value = {x: r, y: g, z: b};    this.dirty = true;  }});Object.defineProperty(ColorReplaceFilter.prototype, 'range', {  set: function (value) {    this.uniforms.range.value = value;    this.dirty = true;  }});

My usage is:

var shader = new ColorReplaceFilter(0xCBCBCB, 0xff7700, 0.1);someSprite.shader = shader;

What am i doing wrong? I'm using the latest pixi v3

Share this post


Link to post
Share on other sites

The shader he wrote was for v2, so that is the biggest problem :)

 

https://github.com/pixijs/pixi-extra-filters

 

I've updated and collected some cool shaders there, including the outline filter:

 

https://github.com/pixijs/pixi-extra-filters/blob/master/src/filters/outline/OutlineFilter.js

 

Unfortunately I didn't know about the color-replacement shader. What would be helpful is if you opened a github issue on that repo with a link to the codepen and I can update it and add it to the repo.

Share this post


Link to post
Share on other sites

I think I've got this working now. I wrote another shader that does the same thing, and then I think I finally understood what needed to change (almost nothing) to get the original working again. I've included both below. Take it with a heap of salt, b/c I do not know GLSL. Also one of the main issues I ran into was simply that in webgl a color component of a number is a float from 0.0 to 1.0 -- my game uses numbers from 0 - 255, so if you do something like what I've done, you must convert your colors appropriately.

 

Per pixel color replacement shader (this one expects a number originating from javascript in the format new Float32Array([255, 255, 255, 255]) would be 0xffffff at 100% opacity).

var shader = [  'precision mediump float;',    'varying vec2 vTextureCoord;',  'uniform sampler2D texture;',  'uniform vec4 findColor;',  'uniform vec4 replaceWithColor;',  'uniform float range;',  'bool closeEnough(vec4 a, vec4  {',  ' if (abs(a.r - b.r) < 0.00001 && abs(a.g - b.g) < 0.00001 && abs(a.b - b. < 0.00001) {',  ' return true;',  ' } else { ',  ' return false;',  ' }',  '}',  'void main(void) {',     '  vec4 current = texture2D(texture, vTextureCoord);',     '  if (closeEnough(findColor/255.0, current)) { ',     '    gl_FragColor = replaceWithColor/255.0;',     '  } else {',     '    gl_FragColor = current;',     '  }',  '}']

And here's the v2 color replacement shader updated for v3 (this one wants the numbers to be new Float32Array([1.0, 1.0, 1.0]) note this is a 3 component not a 4 component color, so the alpha is not part of the search/replacement for the colors):

var ColorReplaceFilter = function (findColor, replaceWithColor, range) {    PIXI.AbstractFilter.call(this,     // vertex shader    null,    // fragment shader    [      'precision mediump float;',      'varying vec2 vTextureCoord;',      'uniform sampler2D texture;',      'uniform vec3 findColor;',      'uniform vec3 replaceWithColor;',      'uniform float range;',      'void main(void) {',      '  vec4 currentColor = texture2D(texture, vTextureCoord);',      '  vec3 colorDiff = findColor - (currentColor.rgb / max(currentColor.a, 0.0000000001));',      '  float colorDistance = length(colorDiff);',      '  float doReplace = step(colorDistance, range);',      '  gl_FragColor = vec4(mix(currentColor.rgb, (replaceWithColor + colorDiff) * currentColor.a, doReplace), currentColor.a);',      '}'    ].join('\n'),    // custom unifroms    {      findColor: { type: '3f', value: findColor },      replaceWithColor: { type: '3f', value: replaceWithColor },      range: { type: '1f', value: range }    }  );};ColorReplaceFilter.prototype = Object.create(PIXI.AbstractFilter.prototype);ColorReplaceFilter.prototype.constructor = ColorReplaceFilter;Object.defineProperty(ColorReplaceFilter.prototype, 'findColor', {  set: function (value) {    var r = ((value & 0xFF0000) >> 16) / 255,        g = ((value & 0x00FF00) >> 8) / 255,        b = (value & 0x0000FF) / 255;    this.uniforms.findColor.value = {x: r, y: g, z: b};    this.dirty = true;  }});Object.defineProperty(ColorReplaceFilter.prototype, 'replaceWithColor', {  set: function (value) {    var r = ((value & 0xFF0000) >> 16) / 255,        g = ((value & 0x00FF00) >> 8) / 255,        b = (value & 0x0000FF) / 255;    this.uniforms.replaceWithColor.value = {x: r, y: g, z: b};    this.dirty = true;  }});Object.defineProperty(ColorReplaceFilter.prototype, 'range', {  set: function (value) {    this.uniforms.range.value = value;    this.dirty = true;  }});

I believe that the above shader should be faster and accomplishes the same thing because it uses the step function instead of if/else to decide if a color qualifies for replacement.

 

 

Usage with a 0-255 RGB color:

  var skinHighlight = new ColorReplaceFilter(    new Float32Array([220/255.0, 220/255.0, 220/255.0]),     new Float32Array([225/255.0, 200/255.0, 215/255.0]),    0.1  ); // replace the color (220, 220, 220) with the color (225, 200, 215)  someSprite.filters = [skinHighlight, etc other shaders here];  // or someSprite.shader = skinHightlight

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Recently Browsing   0 members

    No registered users viewing this page.