Search code examples
c#alphagetpixelindexed

Indexed 8bpp image and array of pixels without GetPixel


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?


Solution

  • 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 to Format32bppArgb 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.