Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Color Replacement in XNA C#

Tags:

c#

xna

I load my textures using

Texture2D.FromFile()

then draw them using

spriteBatch.Draw()

But here's the point: I want to change some colors of the image to another ones. So my questions:

  1. How to change single color of the image to another single color (eg. blue to red).

  2. In fact, what I really want to do is changing group of colors to another group of colors. For example red and similar hues to red to blue and similar hues to blue. You can do this for example in Corel PHOTO-PAINT ("Replace Color").

Please have in mind, that I'm a beginner in XNA. Best regards, Jack

EDIT:

Thank you very much for help, guys. Callum's answer is very helpful indeed. But I'm wondering is there a built-in function to solve my second problem, because writing your own may be time-consuming. And I think, that kind of function may be very useful. Something like:

color.SetNewColor(Color color_from, Color color_to, int range)

That kind of function, as I've said before, is built in Corel PHOTO-PAINT. To explain it better, here is the example of what I'm talking about:

link text

So, I only set color_from, color_to and range. I think it works like that: it checks every color of the image, if it is in range of color_from, it is changed to adequate color in hue of color_to.

like image 922
Jack Avatar asked Jul 15 '10 12:07

Jack


2 Answers

I assume you mean change individual pixels? In that case use the GetData() and SetData() methods of the Texture2D class.


For example, you can get an array containing the colours of the individual pixels by doing this:

// Assume you have a Texture2D called texture

Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);

// You now have a packed array of Colors. 
// So, change the 3rd pixel from the right which is the 4th pixel from the top do:

data[4*texture.Width+3] = Color.Red;

// Once you have finished changing data, set it back to the texture:

texture.SetData(data);

Note you can use the other overloads of GetData() to select only a section.


So, to replace each pixel of a specified colour to another colour:

// Assume you have a Texture2D called texture, Colors called colorFrom, colorTo

Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);

for(int i = 0; i < data.Length; i++)
    if(data[i] == colorFrom)
        data[i] = colorTo;

texture.SetData(data);

To see if hues are similar, try this method:

private bool IsSimilar(Color original, Color test, int redDelta, int blueDelta, int greenDelta)
{
    return Math.Abs(original.R - test.R) < redDelta && Math.Abs(original.G - test.G) < greenDelta && Math.Abs(original.B - test.B) < blueDelta;
}

where *delta is the tolerance of change for each colour channel that you want to accept.


To answer your edit, no there is a built in function, but you can just use a mixture of ideas from the two sections above:

Color[] data = new Color[texture.Width * texture.Height];
texutre.GetData(data);

for(int i = 0; i < data.Length; i++)
    if(IsSimilar(data[i], colorFrom, range, range, range))
        data[i] = colorTo;

texture.SetData(data);
like image 80
Callum Rogers Avatar answered Nov 09 '22 18:11

Callum Rogers


Moving data between the GPU and CPU by using GetData and SetData is an expensive operation. If there are a limited number of colors, you could use a pixel shader effect when rendering to the screen. You can pass an effect to SpriteBatch.Begin:

sampler2D input : register(s0);

/// <summary>The color used to tint the input.</summary>
/// <defaultValue>White</defaultValue>
float4 FromColor : register(C0);

/// <summary>The color used to tint the input.</summary>
/// <defaultValue>Red</defaultValue>
float4 ToColor : register(C1);

/// <summary>Explain the purpose of this variable.</summary>
/// <minValue>05/minValue>
/// <maxValue>10</maxValue>
/// <defaultValue>3.5</defaultValue>
float4 main(float2 uv : TEXCOORD) : COLOR 
{ 
    float4 Color; 
    Color= tex2D(input , uv.xy); 
    if (Color.r == FromColor.r && Color.g == FromColor.g && Color.b == FromColor.b)
        return ToColor;
    return Color; 
}

technique Technique1
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 main();
    }
}

Create your effect in your LoadContent method:

colorSwapEffect = Content.Load<Effect>(@"Effects\ColorSwap");
colorSwapEffect.Parameters["FromColor"].SetValue(Color.White);
colorSwapEffect.Parameters["ToColor"].SetValue(Color.Red);

And pass the effect to your call to SpriteBatch.Begin():

sprite.Begin(0, BlendState.Opaque, SamplerState.PointWrap, 
    DepthStencilState.Default, RasterizerState.CullNone, colorSwapEffect);

For what you really want to do, you can swap the red and blue channels even more easily. Change your pixel shader's main() function to this, which swaps b (blue) and r (red):

float4 main(float2 uv : TEXCOORD) : COLOR 
{ 
    float4 Color;
    Color= tex2D(input , uv.xy);
    return float4(Color.b, Color.g, Color.r, Color.a);
}
like image 34
Jason Goemaat Avatar answered Nov 09 '22 18:11

Jason Goemaat