C# - 正确加载索引彩色图像文件

2023-12-24

所以我创建了一个索引颜色,每像素 8 位 PNG(我已经用 ImageMagick 检查了格式是否正确),我想将它从磁盘加载到System.Drawing.Bitmap同时保留 8bpp 像素格式,以便查看(和操作)其调色板。但是,如果我创建这样的位图:

Bitmap bitmap = new Bitmap("indexed-image.png");

生成的位图会自动转换为 32bpp 图像格式,并且 bitmap.Palette.Entries 字段显示为空。

“如何在 C# 中将 32bpp 图像转换为 8bpp?”问题的答案StackOverflow 上说这可能是将其转换回 8bpp 的有效方法:

bitmap = bitmap.Clone(new Rectangle(0, 0, bitmap.Width, bitmap.Height), PixelFormat.Format8bppIndexed);

然而,这会产生不正确的结果,因为调色板中的某些颜色完全是错误的。

如何将图像本地加载为 8bpp,或者至少正确地将 32bpp 转换为 8bpp?


我也遇到了这个问题,似乎任何调色板的PNG图像包含透明度无法加载为 .Net 框架调色板,尽管 .Net 函数可以完美地write这样的文件。相反,如果文件是 gif 格式,或者调色板 png 具有no透明度。

调色板 png 中的透明度通过在标题中添加可选的“tRNS”块来实现,以指定每个调色板条目的 alpha。 .Net 类正确地读取和应用它,所以我真的不明白为什么他们坚持随后将图像转换为 32 位。

png 格式的结构相当简单;在标识字节之后,每个块是内容大小的 4 个字节(大端),然后是块 id 的 4 个 ASCII 字符,然后是块内容本身,最后是 4 字节块 CRC 值(同样,保存为 big-endian) - 字节序)。

鉴于这种结构,解决方案相当简单:

  • 将文件读入字节数组。
  • 通过分析标题确保它是调色板 png 文件。
  • 通过从块头跳转到块头来找到“tRNS”块。
  • 从块中读取 alpha 值。
  • 创建一个包含图像数据的新字节数组,但删除“tRNS”块。
  • 创建Bitmap对象使用MemoryStream根据调整后的字节数据创建,从而产生正确的 8 位图像。
  • 使用提取的 Alpha 数据修复调色板。

如果你正确地进行了检查和后备,你可以使用此函数加载任何图像,如果它碰巧识别为带有透明度信息的调色板 png,它将执行修复。

/// <summary>
/// Image loading toolset class which corrects the bug that prevents paletted PNG images with transparency from being loaded as paletted.
/// </summary>
public class BitmapHandler
{

    private static Byte[] PNG_IDENTIFIER = {0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A};

    /// <summary>
    /// Loads an image, checks if it is a PNG containing palette transparency, and if so, ensures it loads correctly.
    /// The theory on the png internals can be found at http://www.libpng.org/pub/png/book/chapter08.html
    /// </summary>
    /// <param name="data">File data to load.</param>
    /// <returns>The loaded image.</returns>
    public static Bitmap LoadBitmap(Byte[] data)
    {
        Byte[] transparencyData = null;
        if (data.Length > PNG_IDENTIFIER.Length)
        {
            // Check if the image is a PNG.
            Byte[] compareData = new Byte[PNG_IDENTIFIER.Length];
            Array.Copy(data, compareData, PNG_IDENTIFIER.Length);
            if (PNG_IDENTIFIER.SequenceEqual(compareData))
            {
                // Check if it contains a palette.
                // I'm sure it can be looked up in the header somehow, but meh.
                Int32 plteOffset = FindChunk(data, "PLTE");
                if (plteOffset != -1)
                {
                    // Check if it contains a palette transparency chunk.
                    Int32 trnsOffset = FindChunk(data, "tRNS");
                    if (trnsOffset != -1)
                    {
                        // Get chunk
                        Int32 trnsLength = GetChunkDataLength(data, trnsOffset);
                        transparencyData = new Byte[trnsLength];
                        Array.Copy(data, trnsOffset + 8, transparencyData, 0, trnsLength);
                        // filter out the palette alpha chunk, make new data array
                        Byte[] data2 = new Byte[data.Length - (trnsLength + 12)];
                        Array.Copy(data, 0, data2, 0, trnsOffset);
                        Int32 trnsEnd = trnsOffset + trnsLength + 12;
                        Array.Copy(data, trnsEnd, data2, trnsOffset, data.Length - trnsEnd);
                        data = data2;
                    }
                }
            }
        }
        using(MemoryStream ms = new MemoryStream(data))
        using(Bitmap loadedImage = new Bitmap(ms))
        {
            if (loadedImage.Palette.Entries.Length != 0 && transparencyData != null)
            {
                ColorPalette pal = loadedImage.Palette;
                for (int i = 0; i < pal.Entries.Length; i++)
                {
                    if (i >= transparencyData.Length)
                        break;
                    Color col = pal.Entries[i];
                    pal.Entries[i] = Color.FromArgb(transparencyData[i], col.R, col.G, col.B);
                }
                loadedImage.Palette = pal;
            }
            // Images in .Net often cause odd crashes when their backing resource disappears.
            // This prevents that from happening by copying its inner contents into a new Bitmap object.
            return CloneImage(loadedImage, null);
        }
    }

    /// <summary>
    /// Finds the start of a png chunk. This assumes the image is already identified as PNG.
    /// It does not go over the first 8 bytes, but starts at the start of the header chunk.
    /// </summary>
    /// <param name="data">The bytes of the png image.</param>
    /// <param name="chunkName">The name of the chunk to find.</param>
    /// <returns>The index of the start of the png chunk, or -1 if the chunk was not found.</returns>
    private static Int32 FindChunk(Byte[] data, String chunkName)
    {
        if (data == null)
            throw new ArgumentNullException("data", "No data given!");
        if (chunkName == null)
            throw new ArgumentNullException("chunkName", "No chunk name given!");
        // Using UTF-8 as extra check to make sure the name does not contain > 127 values.
        Byte[] chunkNamebytes = Encoding.UTF8.GetBytes(chunkName);
        if (chunkName.Length != 4 || chunkNamebytes.Length != 4)
            throw new ArgumentException("Chunk name must be 4 ASCII characters!", "chunkName");
        Int32 offset = PNG_IDENTIFIER.Length;
        Int32 end = data.Length;
        Byte[] testBytes = new Byte[4];
        // continue until either the end is reached, or there is not enough space behind it for reading a new chunk
        while (offset + 12 < end)
        {
            Array.Copy(data, offset + 4, testBytes, 0, 4);
            if (chunkNamebytes.SequenceEqual(testBytes))
                return offset;
            Int32 chunkLength = GetChunkDataLength(data, offset);
            // chunk size + chunk header + chunk checksum = 12 bytes.
            offset += 12 + chunkLength;
        }
        return -1;
    }

    private static Int32 GetChunkDataLength(Byte[] data, Int32 offset)
    {
        if (offset + 4 > data.Length)
            throw new IndexOutOfRangeException("Bad chunk size in png image.");
        // Don't want to use BitConverter; then you have to check platform endianness and all that mess.
        Int32 length = data[offset + 3] + (data[offset + 2] << 8) + (data[offset + 1] << 16) + (data[offset] << 24);
        if (length < 0)
            throw new IndexOutOfRangeException("Bad chunk size in png image.");
        return length;
    }

    /// <summary>
    /// Clones an image object to free it from any backing resources.
    /// Code taken from http://stackoverflow.com/a/3661892/ with some extra fixes.
    /// </summary>
    /// <param name="sourceImage">The image to clone.</param>
    /// <returns>The cloned image.</returns>
    public static Bitmap CloneImage(Bitmap sourceImage)
    {
        Rectangle rect = new Rectangle(0, 0, sourceImage.Width, sourceImage.Height);
        Bitmap targetImage = new Bitmap(rect.Width, rect.Height, sourceImage.PixelFormat);
        targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
        BitmapData sourceData = sourceImage.LockBits(rect, ImageLockMode.ReadOnly, sourceImage.PixelFormat);
        BitmapData targetData = targetImage.LockBits(rect, ImageLockMode.WriteOnly, targetImage.PixelFormat);
        Int32 actualDataWidth = ((Image.GetPixelFormatSize(sourceImage.PixelFormat) * rect.Width) + 7) / 8;
        Int32 h = sourceImage.Height;
        Int32 origStride = sourceData.Stride;
        Int32 targetStride = targetData.Stride;
        Byte[] imageData = new Byte[actualDataWidth];
        IntPtr sourcePos = sourceData.Scan0;
        IntPtr destPos = targetData.Scan0;
        // Copy line by line, skipping by stride but copying actual data width
        for (Int32 y = 0; y < h; y++)
        {
            Marshal.Copy(sourcePos, imageData, 0, actualDataWidth);
            Marshal.Copy(imageData, 0, destPos, actualDataWidth);
            sourcePos = new IntPtr(sourcePos.ToInt64() + origStride);
            destPos = new IntPtr(destPos.ToInt64() + targetStride);
        }
        targetImage.UnlockBits(targetData);
        sourceImage.UnlockBits(sourceData);
        // For indexed images, restore the palette. This is not linking to a referenced
        // object in the original image; the getter of Palette creates a new object when called.
        if ((sourceImage.PixelFormat & PixelFormat.Indexed) != 0)
            targetImage.Palette = sourceImage.Palette;
        // Restore DPI settings
        targetImage.SetResolution(sourceImage.HorizontalResolution, sourceImage.VerticalResolution);
        return targetImage;
    }

}

不过,这个方法似乎只能解决 8 位和 4 位 png 的问题。 Gimp 重新保存的只有 4 种颜色的 png 变成了 2 位 png,尽管不包含任何透明度,但仍然以 32 位颜色打开。

事实上,保存调色板大小也存在类似的问题; .Net 框架可以完美地处理加载非完整大小调色板的 png 文件(8 位小于 256,4 位小于 16),但在保存文件时,它会将其填充到完整调色板。这可以通过类似的方式修复,通过在保存到后对块进行后处理MemoryStream https://stackoverflow.com/a/48165612/395685。不过,这需要计算 CRC。

另请注意,虽然这应该能够加载任何图像类型,但它无法在动画 GIF 文件上正常工作,因为CloneImage最后使用的函数仅复制单个图像。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C# - 正确加载索引彩色图像文件 的相关文章

  • “构建”构建我的项目,“构建解决方案”则不构建

    我刚刚开始使用VS2010 我有一个较大的解决方案 已从 VS2008 成功迁移 我已将一个名为 Test 的控制台应用程序项目添加到解决方案中 选择构建 gt 构建解决方案不编译新项目 选择构建 gt 构建测试确实构建了项目 在失败的情况
  • 为什么 C# Array.BinarySearch 这么快?

    我已经实施了一个很简单用于在整数数组中查找整数的 C 中的 binarySearch 实现 二分查找 static int binarySearch int arr int i int low 0 high arr Length 1 mid
  • GLKit的GLKMatrix“列专业”如何?

    前提A 当谈论线性存储器中的 列主 矩阵时 列被一个接一个地指定 使得存储器中的前 4 个条目对应于矩阵中的第一列 另一方面 行主 矩阵被理解为依次指定行 以便内存中的前 4 个条目指定矩阵的第一行 A GLKMatrix4看起来像这样 u
  • 在结构中使用 typedef 枚举并避免类型混合警告

    我正在使用 C99 我的编译器是 IAR Embedded workbench 但我认为这个问题对于其他一些编译器也有效 我有一个 typedef 枚举 其中包含一些项目 并且我向该新类型的结构添加了一个元素 typedef enum fo
  • 在哪里可以找到列出 SSE 内在函数操作的官方参考资料?

    是否有官方参考列出了 GCC 的 SSE 内部函数的操作 即 头文件中的函数 除了 Intel 的 vol 2 PDF 手册外 还有一个在线内在指南 https www intel com content www us en docs in
  • 查找c中结构元素的偏移量

    struct a struct b int i float j x struct c int k float l y z 谁能解释一下如何找到偏移量int k这样我们就可以找到地址int i Use offsetof 找到从开始处的偏移量z
  • 嵌套接口:将 IDictionary> 转换为 IDictionary>?

    我认为投射一个相当简单IDictionary
  • 类模板参数推导 - clang 和 gcc 不同

    下面的代码使用 gcc 编译 但不使用 clang 编译 https godbolt org z ttqGuL template
  • OleDbDataAdapter 未填充所有行

    嘿 我正在使用 DataAdapter 读取 Excel 文件并用该数据填充数据表 这是我的查询和连接字符串 private string Query SELECT FROM Sheet1 private string ConnectStr
  • 不同枚举类型的范围和可转换性

    在什么条件下可以从一种枚举类型转换为另一种枚举类型 让我们考虑以下代码 include
  • 将 VSIX 功能添加到 C# 类库

    我有一个现有的单文件生成器 位于 C 类库中 如何将 VSIX 项目级功能添加到此项目 最终目标是编译我的类库项目并获得 VSIX 我实际上是在回答我自己的问题 这与Visual Studio 2017 中的单文件生成器更改 https s
  • 在 ASP.NET 5 中使用 DI 调用构造函数时解决依赖关系

    Web 上似乎充斥着如何在 ASP NET 5 中使用 DI 的示例 但没有一个示例显示如何调用构造函数并解决依赖关系 以下只是众多案例之一 http social technet microsoft com wiki contents a
  • C++ OpenSSL 导出私钥

    到目前为止 我成功地使用了 SSL 但遇到了令人困惑的障碍 我生成了 RSA 密钥对 之前使用 PEM write bio RSAPrivateKey 来导出它们 然而 手册页声称该格式已经过时 实际上它看起来与通常的 PEM 格式不同 相
  • 将多个表映射到实体框架中的单个实体类

    我正在开发一个旧数据库 该数据库有 2 个具有 1 1 关系的表 目前 我为每个定义的表定义了一种类型 1Test 1Result 我想将这些特定的表合并到一个类中 当前的类型如下所示 public class Result public
  • 重载<<的返回值

    include
  • 将控制台重定向到 .NET 程序中的字符串

    如何重定向写入控制台的任何内容以写入字符串 对于您自己的流程 Console SetOut http msdn microsoft com en us library system console setout aspx并将其重定向到构建在
  • 如何将服务器服务连接到 Dynamics Online

    我正在修改内部管理应用程序以连接到我们的在线托管 Dynamics 2016 实例 根据一些在线教程 我一直在使用OrganizationServiceProxy out of Microsoft Xrm Sdk Client来自 SDK
  • Windows 和 Linux 上的线程

    我在互联网上看到过在 Windows 上使用 C 制作多线程应用程序的教程 以及在 Linux 上执行相同操作的其他教程 但不能同时用于两者 是否存在即使在 Linux 或 Windows 上编译也能工作的函数 您需要使用一个包含两者的实现
  • C++ 中类级 new 删除运算符的线程安全

    我在我的一门课程中重新实现了新 删除运算符 现在我正在使我的代码成为多线程 并想了解这些运算符是否也需要线程安全 我在某处读到 Visual Studio 中默认的 new delete 运算符是线程安全的 但这对于我的类的自定义 new
  • C++ 标准是否指定了编译器的 STL 实现细节?

    在写答案时this https stackoverflow com questions 30909296 can you put a pimpl class inside a vector我遇到了一个有趣的情况 这个问题演示了这样一种情况

随机推荐

  • 使 FAB 响应软键盘显示/隐藏更改

    我看过各种关于 FAB 响应屏幕底部 Snackbar 弹出窗口以及滚动敏感 FAB 的帖子 但是否有一些实施FloatingActionButton Behavior 或类似 将 FAB 移至键盘上方当它出现时 现在 当我单击某个按钮时
  • 将 IE 窗口置于屏幕前面

    我正在动态创建新的 IE 浏览器实例 并从那里打开一个 aspx 页面 一切正常 但浏览器没有在屏幕前面弹出 当我从那里单击它时 能够在任务栏中看到 Aspx 页面 它会出现在前面 如何在 IE 创建后立即将该页面显示在所有屏幕的前面 我已
  • 如何处理来自不同时区的日期时间

    我有一个 django 应用程序 它在数据库 postgres 中存储 UTC 的日期时间 它在世界各地都有用户 但在应用程序逻辑中 我根据本地时间范围进行了一些验证 即用户在瓜亚基尔并且整个周日都发生了一些事情 我在执行它时遇到问题并进行
  • 调用线程无法访问该对象,因为另一个线程拥有它

    我正在尝试从 PowerShell 检索打印队列列表 如下所示 但我越来越 The calling thread cannot access this object because a different thread owns it 发生
  • 如何在Python中进行二次排序?

    如果我有一个数字列表 4 2 5 1 3 我想先按某个功能对其进行排序f然后对于具有相同值的数字f我希望它按数字的大小排序 这段代码似乎不起作用 list5 sorted list5 list5 sorted list5 key lambd
  • webpack 在react.js 中无法正常工作

    我使用创建了一个 hello world 反应应用程序create react app命令 然后我尝试使用运行相同的文件webpack 但它不能正常工作 比如 ico css文件是not rendering到屏幕上 请帮我解决这个问题 we
  • 在 Observable Angular js 2 中迭代 json 字符串

    以下是我的html代码 tr td c name td td c skill td tr 在我的 json 中 name abc skill xyz 这是可行的 但我需要迭代这个 json 字符串 var obj a 1 b 2 for v
  • 如何在运行时重新转换类?

    我正在尝试修改一个已加载到 JVM 中的类 我找到的解决方案是这样的 将代理附加到 PID 指定的 JVM 例如8191 代码 AttachTest 从 JVM 中已加载的类中找到您要修改的类 例如 8191 使用仪器添加变压器 代码 Ag
  • C++ 进程因状态 3 混乱而终止

    我对编程非常陌生 但在过去一周左右的时间里一直在关注 C 教程并积累了许多 PDF 来帮助我 我在其中或网上找不到任何足够清楚地回答我的问题的内容 请原谅我的新手 相关代码 日志文件 hpp HEADER CLASS INTERFACE F
  • 检索 Linkedin 视频帖子 (ugcPost API) 的缩略图

    我尝试使用 ugcPost api 检索视频帖子的缩略图 但没有成功 我总是检索一个空的缩略图数组 关于文档检索 UGC 帖子 https learn microsoft com en us linkedin marketing integ
  • 什么时候在keras中使用sample_weights合适?

    根据这个question https stackoverflow com questions 43459317 keras class weight vs sample weights in the fit generator 我了解到cl
  • SonarQube:无法停用缺少质量配置文件的规则

    我的 SonarQube 中有一条规则 在搜索列表中没有与其关联的质量配置文件 红色框here https i stack imgur com UHnQG png 当我尝试改变它时我得到这个错误 https i stack imgur co
  • 通过缓动水平滑动 div

    我希望实现一个隐藏 显示 div 其中鼠标输入 div 显示 但以从左到右滑动的方式缓动 另外 我需要页面关注刚刚滑出 可见的新 div 这是我的脚本 关于我需要添加什么的任何想法
  • Delayed_job 锁定但不处理

    我正在尝试解决delayed job 的问题 由于某种原因 我看到很多作业locked by和locked at 但队列中没有任何内容被处理 有什么建议可以解释为什么会发生这种情况或如何让它继续下去吗 我正在使用 Rails 2 3 11
  • AngularJS - ngOptions:如何按组名称然后按标签排序

    假设我有以下形式的数据数组 var data group GroupA label BB group GroupB label DD 我的绑定会是这样的
  • 签署使用 maven- assembly 插件创建的 jar 文件

    我想构建一个程序集然后对其进行签名 我的问题是 jarsigner 不签署程序集 只签署独立的 jar 文件 你能告诉我问题是什么吗 在使用 Ant 多年后 Maven 对我来说似乎很 神奇 我看不到插件如何协作以及相互传递信息的方式 执行
  • HTML 正文中的样式和脚本标记...为什么不呢?

    这与这个问题 https stackoverflow com questions 225828 但不是因为它与电子邮件无关 在许多情况下 尤其是在使用 CMS 或其他人的框架时 嵌入要容易得多
  • 导入错误:没有名为“加密”的模块

    我在 Windows 7 上安装了 python 3 4 当尝试使用 paramiko 时出现此错误 import paramiko File C Python34 lib site packages paramiko 2 0 2 py3
  • 是否应该为已发布的应用程序关闭 NSZombieEnabled?

    With NSZombieEnabled打开它会提供一些防范EXC BAD ACCESS运行时发生的问题 我正在双重努力以确保没有 很少内存泄漏 但我可能会过度释放 所以有NSZombieEnabled打开将有助于防止这种情况 对吗 或者与
  • C# - 正确加载索引彩色图像文件

    所以我创建了一个索引颜色 每像素 8 位 PNG 我已经用 ImageMagick 检查了格式是否正确 我想将它从磁盘加载到System Drawing Bitmap同时保留 8bpp 像素格式 以便查看 和操作 其调色板 但是 如果我创建