【问题标题】:Indexed 8bpp image and array of pixels without GetPixel没有 GetPixel 的索引 8bpp 图像和像素数组
【发布时间】:2018-12-19 01:41:47
【问题描述】:

我需要在调色板中获取位图每个像素的索引(我有一个索引为 8bpp 的图像)。现在我使用以下方式:

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)));

但这种方式工作得非常慢,因为我使用了几张高分辨率图像。

我的问题是:

1)有没有办法加快循环过程得到每个像素的RGBA值?

2) 或许有更优化的方法来获取调色板中图像的颜色索引?

【问题讨论】:

  • 您正在将数据添加到每个循环中的列表中。在构造函数中预先分配大小: List data = new List(1000);其中 1000 是位图的高度 * 宽度。
  • @jdweng,我不明白它对我有什么帮助。
  • 添加到列表方法很慢,因为在每个循环中,您必须调用类的构造函数。在进入循环之前创建列表会更快。然后使用:data[(i * bitmap.Width) + j] = (byte)pixels[k].IndexOf(bitmap.GetPixel(j, i));
  • 你看到this post了吗,Jose的答案可能就是你想要的。
  • @jdweng 首先,它必须是字节数组而不是List,因为List每次都有0个元素,并且在data[(i * bitmap.Width) + j] = (byte)pixels[k].IndexOf(bitmap.GetPixel(j, i));期间抛出ArgumentOutOfRangeException。其次,这种方式写入数组不正确的二进制数据,与我收到的不同。第三,我没有注意到明显的时差。

标签: c# alpha getpixel indexed


【解决方案1】:

也许这样的事情会更快。推理,GetbitsSetbits 非常慢,每个都在内部调用锁定位来锁定内部存储器。最好一次性全部完成

使用LockBitsunsafefixedDictionary

为了测试结果,我使用了这张图片

来自

我根据您的原始版本测试结果,它们是相同的

基准测试

----------------------------------------------------------------------------
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
----------------------------------------------------------------------------

测试 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 % |
---------------------------------------------------------------------------

完整代码

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;
   }
}

总结

无论如何,我都不是图像专家,如果这不起作用,那么您的索引图像可能会有所不同,我不明白

更新

要检查像素格式,如果它有调色板,您可以使用以下方法

bmp.PixelFormat
bmp.Palette.Entries.Any()

更新2

Vlad i Slav 的工作解决方案如下

我需要将 PixelFormat.Format32bppPArgb 替换为 Format32bppArgb 和 添加此检查

if (pixels.ContainsKey(*p)) 
   *d = (byte)pixels[*p];
else 
   *d = 0;. 

还需要从调色板中获取不同的值,因为我一直 在那里给出一些错误。

【讨论】:

  • @VladiSlav 为了方便起见,仍然应该快 1000 倍,这再次没有经过测试,但是应该可以工作
  • Vlad i Slav 给我一个小时回家做作业,我给你写一个工作测试版
  • @VladiSlav 你必须给我一个失败的图片的链接,这样我才能看它,也许调色板不包含 alpha 通道?
  • @VladiSlav 调色板是空的
  • 很抱歉图片不正确。这是透明的 png8 图像:link。我想在调色板中获取像素颜色的索引。仅此而已。
【解决方案2】:

如果你提前查看pixelformat,确定是PixelFormat.Format8bppIndexed,你可以直接在LockBits调用中使用bmp.PixelFormat,直接获取real像素值。这也避免了在调色板包含重复颜色的情况下得到错误的索引。

不过,你必须小心步幅; .Net 中图像中每一行的字节长度为rounded up to the next multiple of 4 bytes。例如,在宽度为 386 的图像上(如 your penguin),实际数据将是每行 388 个字节。这就是他们所说的“大步”。为了避免出现问题,而是得到完全紧凑的数据,复制每行的输入数据,在实际使用的行数据长度的块中,同时将读取的指针向前移动BitmapData 对象给出的完整步幅。

最小步幅通常计算为(bpp * width + 7) / 8。当然,对于 8 位图像,这将简单地等于宽度,因为它是每个像素 1 个字节,但我为它编写的函数支持任何位深度,甚至低于 8bpp,这就是它进行该计算的原因。

完整功能:

/// <summary>
/// Gets the raw bytes from an image.
/// </summary>
/// <param name="sourceImage">The image to get the bytes from.</param>
/// <param name="stride">Stride of the retrieved image data.</param>
/// <param name="collapseStride">Collapse the stride to the minimum required for the image data.</param>
/// <returns>The raw bytes of the image.</returns>
public static Byte[] GetImageData(Bitmap sourceImage, out Int32 stride, Boolean collapseStride)
{
    if (sourceImage == null)
        throw new ArgumentNullException("sourceImage", "Source image is null!");
    Int32 width = sourceImage.Width;
    Int32 height = sourceImage.Height;
    BitmapData sourceData = sourceImage.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadOnly, sourceImage.PixelFormat);
    stride = sourceData.Stride;
    Byte[] data;
    if (collapseStride)
    {
        Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * width) + 7) / 8;
        Int64 sourcePos = sourceData.Scan0.ToInt64();
        Int32 destPos = 0;
        data = new Byte[actualDataWidth * height];
        for (Int32 y = 0; y < height; ++y)
        {
            Marshal.Copy(new IntPtr(sourcePos), data, destPos, actualDataWidth);
            sourcePos += stride;
            destPos += actualDataWidth;
        }
        stride = actualDataWidth;
    }
    else
    {
        data = new Byte[stride * height];
        Marshal.Copy(sourceData.Scan0, data, 0, data.Length);
    }
    sourceImage.UnlockBits(sourceData);
    return data;
}

在你的情况下,这可以像这样简单地调用:

if (image.PixelFormat == PixelFormat.Format8bppIndexed)
{
    Int32 stride;
    Byte[] rawData = GetImageData(image, out stride, true);
    // ... do whatever you want with that image data.
}

请注意,正如我在另一个答案的评论中所说,.Net 框架存在一个错误,该错误会阻止包含调色板透明度信息的 8 位 PNG 图像正确加载为 8 位。要解决该问题,请查看以下答案:

A: How to read 8-bit PNG image as 8-bit PNG image only?

【讨论】:

    猜你喜欢
    • 2011-07-07
    • 2011-10-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-02-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多