Problem
You have an image of unit (sprite) in your game and you would like to dynamically change selected colors. You would like to get units that will look the same for one player and different than others. Let’s say Player one has units with red shield, Player two uses blue color.
Red and blue dummy example:


You can use two different images prepared by designer but then your application size will be increased. If your unit’s graphic is 10KB and you support ten players with ten colors, you get 100KB size. Now, if your animation is in 8 different directions, you will have 800KB instead of 80KB and it’s just for one sprite!
RGB Solution
The most convenient way to do that (as far as I know) is to use ColorPalette class but unfortunately it’s not supported in new Windows 8. If we don’t have access to colors, we can only go through pixels and change them manually.
We don’t have SetPixel and GetPixel methods on Bitmap or WriteableBitmap classes so following example will NOT work:
Bitmap bitmap = LoadBitmap("Sprite.png");
for (int x = 0; x < bitmap.Width; x++)
{
for (int y = 0; y < bitmap.Height; y++)
{
if (bitmap.GetPixel(x, y) == Color.Red)
{
bitmap.SetPixel(x, y, Color.Blue);
}
}
}
We have to go really deep and work on pixels directly. We will detect color to swap and replace pixel’s values.
XAML:
<Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
<StackPanel>
<Button Content="Load image (Red to Cyan)" Click="Button_Click_1" />
<Image x:Name="image" Stretch="None"/>
</StackPanel>
</Grid>
Load Image and call “SwitchColors” method:
private async void Button_Click_1(object sender, RoutedEventArgs e)
{
FileOpenPicker picker = new FileOpenPicker();
picker.SuggestedStartLocation = PickerLocationId.PicturesLibrary;
picker.FileTypeFilter.Add(".png");
StorageFile file = await picker.PickSingleFileAsync();
if (file != null)
{
using (IRandomAccessStream fileStream = await file.OpenAsync(Windows.Storage.FileAccessMode.Read))
{
BitmapDecoder decoder = await BitmapDecoder.CreateAsync(fileStream);
PixelDataProvider pixelData = await decoder.GetPixelDataAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight,
new BitmapTransform(),
ExifOrientationMode.IgnoreExifOrientation,
ColorManagementMode.DoNotColorManage);
byte[] sourcePixels = pixelData.DetachPixelData();
await ThreadPool.RunAsync(new WorkItemHandler(
(IAsyncAction action) =>
{
sourcePixels = SwitchColor(sourcePixels, decoder.PixelWidth, decoder.PixelHeight, Colors.Red, Colors.Cyan);
}
));
var writeableBitmap = new WriteableBitmap((int)decoder.PixelWidth, (int)decoder.PixelHeight);
using (Stream stream = writeableBitmap.PixelBuffer.AsStream())
{
await stream.WriteAsync(sourcePixels, 0, sourcePixels.Length);
}
// Redraw the image
image.Source = writeableBitmap;
}
}
}
Method “SwitchColors”:
private byte[] SwitchColor(byte[] sourcePixels, uint pixelWidth, uint pixelHeight, Color colorFrom, Color colorTo)
{
int resultIndex = 0;
// 4 bytes required for each pixel
byte cFromB = colorFrom.B;
byte cFromG = colorFrom.G;
byte cFromR = colorFrom.R;
byte cFromA = colorFrom.A;
byte cToB = colorTo.B;
byte cToG = colorTo.G;
byte cToR = colorTo.R;
byte cToA = colorTo.A;
for (int y = 0; y < pixelHeight; y++)
{
for (int x = 0; x < pixelWidth; x++)
{
if (sourcePixels[resultIndex] == cFromG && sourcePixels[resultIndex + 1] == cFromB &&
sourcePixels[resultIndex + 2] == cFromR && sourcePixels[resultIndex + 3] == cFromA)
{
sourcePixels[resultIndex] = cToB;
sourcePixels[resultIndex + 1] = cToG;
sourcePixels[resultIndex + 2] = cToR;
sourcePixels[resultIndex + 3] = cToA;
}
resultIndex += 4;
}
}
return sourcePixels;
}
Results
Image before and after (Red to Blue)

Sprite before and after (Red to Green and to Cyan)



I am changing only one single color. For our applications we will have some shadows and we would need to do that for at least few more (e.g. Red: {255, 255, 0, 0}, {255, 254, 0, 0}, {255, 253, 0, 0}).
There is also alternative to use HSL but that’s different story, not for today.
Optimization
If you know that Alpha channel will not change, don’s set it in the IF statement. You might also know that shield on the image starts on specific pixel {e.g. 300, 300}, so don’t iterate from 0 till end of the image.
Resources
- Download source code: link
Thanks!
For the shades you could use a function to look at the distance from desired color:
if (((R2-R1)^2 + (G2-G1)^2…) < limit) then Recolor(…);
Then in Recolor you could just switch components (e.g. RG) or so. That shall be cheaper than HSL conversion and look as good.
are you sure this works?
when colorFrom is Colors.Red, in SwitchColor this breaks down to:
colorFrom
{#FFFF0000}
A: 255
B: 0
G: 0
R: 255
however when i have a red pixel these are reported as:
sourcePixels[0] = 36
sourcePixels[1] = 28
sourcePixels[2] = 237
sourcePixels[3] = 255
please advise?
thanks.
actually, ignore the last post – my sample image had dodgy colors!
输入您的评论 您可以使用这些HTML标签:
how would i set a pixel as transparent?
ignore that last one as well.
ok, heres a real question (lol).
your code above does not seem to preserve transparency?
i.e. if the original image has any transparent bits they are rendered as white on the output? hope you can help! thanks.
grrr. i find im having to do as below. works, but feels wrong.
byte cTransparentB = 255;
byte cTransparentG = 255;
byte cTransparentR = 255;
byte cTransparentA = 0;
for (int y = 0; y < pixelHeight; y++)
{
for (int x = 0; x < pixelWidth; x++)
{
if ((sourcePixels[resultIndex] == cFromG && sourcePixels[resultIndex + 1] == cFromB &&
sourcePixels[resultIndex + 2] == cFromR && sourcePixels[resultIndex + 3] == cFromA) ||
(sourcePixels[resultIndex] == cTransparentG && sourcePixels[resultIndex + 1] == cTransparentB &&
sourcePixels[resultIndex + 2] == cTransparentR && sourcePixels[resultIndex + 3] == cTransparentA))
{
sourcePixels[resultIndex] = 0;
sourcePixels[resultIndex + 1] = 0;
sourcePixels[resultIndex + 2] = 0;
sourcePixels[resultIndex + 3] = 0;
}
resultIndex += 4;
}
}