I need to get index of every pixel of my Bitmap in color palette (I have an indexed 8bpp image). Now I use the following way:
List<byte> data = new List<byte>(); // indexes
List<Color> pixels = bitmap.Palette.Entries.ToList(); // palette
for (int i = 0; i <bitmap.Height; i++)
for (int j = 0; j < bitmap.Width; j++)
data.Add((byte)pixels[k].IndexOf(bitmap.GetPixel(j, i)));
but this way works VERY slowly, because I use several high resolution images.
My questions is:
1) Is there a way to speed up the looping process to get the RGBA values of each pixel?
2) Perhaps there is a more optimal way to get color indexes of images in the palette?
Maybe something like this would be faster. The reasoning, Getbits
and Setbits
are extremely slow, each one calls lockbits internally which pins the internal memory. Best just do it all at once
using LockBits
, unsafe
, fixed
, and a Dictionary
To test the results i used this image
from
I tests the results against your original version and they are the same
Benchmarks
----------------------------------------------------------------------------
Mode : Release (64Bit)
Test Framework : .NET Framework 4.7.1 (CLR 4.0.30319.42000)
----------------------------------------------------------------------------
Operating System : Microsoft Windows 10 Pro
Version : 10.0.17134
----------------------------------------------------------------------------
CPU Name : Intel(R) Core(TM) i7-2600 CPU @ 3.40GHz
Description : Intel64 Family 6 Model 42 Stepping 7
Cores (Threads) : 4 (8) : Architecture : x64
Clock Speed : 3401 MHz : Bus Speed : 100 MHz
L2Cache : 1 MB : L3Cache : 8 MB
----------------------------------------------------------------------------
Test 1
--- Random Set ------------------------------------------------------------
| Value | Average | Fastest | Cycles | Garbage | Test | Gain |
--- Scale 1 ------------------------------------------------ Time 8.894 ---
| Mine1 | 5.211 ms | 4.913 ms | 17.713 M | 0.000 B | Pass | 93.50 % |
| Original | 80.107 ms | 75.131 ms | 272.423 M | 0.000 B | Base | 0.00 % |
---------------------------------------------------------------------------
The Full code
public unsafe byte[] Convert(string input)
{
using (var bmp = new Bitmap(input))
{
var pixels = bmp.Palette.Entries.Select((color, i) => new {x = color,i})
.ToDictionary(arg => arg.x.ToArgb(), x => x.i);
// lock the image data for direct access
var bits = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, PixelFormat.Format32bppPArgb);
// create array as we know the size
var data = new byte[bmp.Height * bmp.Width];
// pin the data array
fixed (byte* pData = data)
{
// just getting a pointer we can increment
var d = pData;
// store the max length so we don't have to recalculate it
var length = (int*)bits.Scan0 + bmp.Height * bmp.Width;
// Iterate through the scanlines of the image as contiguous memory by pointer
for (var p = (int*)bits.Scan0; p < length; p++, d++)
//the magic, get the pixel, lookup the Dict, assign the values
*d = (byte)pixels[*p];
}
// unlock the bitmap
bmp.UnlockBits(bits);
return data;
}
}
Summary
I am not a image expert by any means, if this doesn't work there maybe something different about your indexed image that i dont understand
Update
To check the pixel format and if it has a palette you can use the following
bmp.PixelFormat
bmp.Palette.Entries.Any()
Update2
The working solution from Vlad i Slav as follows
I needed to replace
PixelFormat.Format32bppPArgb
toFormat32bppArgb
and to add this checking
if (pixels.ContainsKey(*p))
*d = (byte)pixels[*p];
else
*d = 0;.
Also need to take a distinct values from palette, because I have been give some errors there.