C# 手动解析灰度PNG图片为Bitmap

这篇具有很好参考价值的文章主要介绍了C# 手动解析灰度PNG图片为Bitmap。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

问题:

当直接使用文件路径加载8位灰度PNG图片为Bitmap时,Bitmap的格式将会是Format32bppArgb,而不是Format8bppIndexed,这对一些判断会有影响,所以需要手动解析PNG的数据来构造Bitmap

步骤

1. 判断文件格式

若对PNG文件格式不是很了解,阅读本文前可以参考PNG的文件格式 PNG文件格式详解

简而言之,PNG文件头有8个固定字节来标识它,他们是

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

2. 判断是否为8位灰度图

识别为PNG文件后,需要判断该PNG文件是否为8位的灰度图

在PNG的文件头标识后是PNG文件的第一个数据块IHDR,它的数据域由13个字节组成

域的名称 数据字节数 说明
Width 4 bytes 图像宽度,以像素为单位
Height 4 bytes 图像高度,以像素为单位
Bit depth 1 byte 图像深度:索引彩色图像:1,2,4或8 ;灰度图像:1,2,4,8或16 ;真彩色图像:8或16
ColorType 1 byte 颜色类型:0:灰度图像, 1,2,4,8或16;2:真彩色图像,8或16;3:索引彩色图像,1,2,4或84:带α通道数据的灰度图像,8或16;6:带α通道数据的真彩色图像,8或16
Compression method 1 byte 压缩方法(LZ77派生算法)
Filter method 1 byte 滤波器方法
Interlace method 1 byte 隔行扫描方法:0:非隔行扫描;1: Adam7(由Adam M. Costello开发的7遍隔行扫描方法)

这里我们看颜色深度以及颜色类型就行

 var ihdrData = data[(PNG_IDENTIFIER.Length + 8)..(PNG_IDENTIFIER.Length + 8 + 13)];
 var bitDepth = Convert.ToInt32(ihdrData[8]);
 var colorType = Convert.ToInt32(ihdrData[9]);

这里的data是表示PNG文件的byte数组,+8是因为PNG文件的每个数据块的数据域前都有4个字节的数据域长度和4个字节的数据块类型(名称)


3. 获取全部图像数据块

PNG文件的图像数据由一个或多个图像数据块IDAT构成,并且他们是顺序排列的

这里通过while循环找到所有的IDAT

var compressedSubDats = new List<byte[]>();
var firstDatOffset = FindChunk(data, "IDAT");
var firstDatLength = GetChunkDataLength(data, firstDatOffset);
var firstDat = new byte[firstDatLength];

Array.Copy(data, firstDatOffset + 8, firstDat, 0, firstDatLength);
compressedSubDats.Add(firstDat);

var dataSpan = data.AsSpan().Slice(firstDatOffset + 12 + firstDatLength);
while (Encoding.ASCII.GetString(dataSpan[4..8]) == "IDAT")
{
    var datLength = dataSpan.ReadBinaryInt(0, 4);
    var dat = new byte[datLength];
    dataSpan.Slice(8, datLength).CopyTo(dat);
    compressedSubDats.Add(dat);
    dataSpan = dataSpan.Slice(12 + datLength);
}

var compressedDatLength = compressedSubDats.Sum(a => a.Length);
var compressedDat = new byte[compressedDatLength].AsSpan();
var index = 0;
for (int i = 0; i < compressedSubDats.Count; i++)
{
    var subDat = compressedSubDats[i];
    subDat.CopyTo(compressedDat.Slice(index, subDat.Length));
    index += subDat.Length;
}

4. 解压DAT数据

上一步获得的DAT数据是由Deflate算法压缩后的,我们需要将它解压缩,这里使用.NET自带的DeflateStream进行解压缩

IDAT的数据流以zlib格式存储,结构为

名称 长度
zlib compression method/flags code 1 byte
Additional flags/check bits 1 byte
Compressed data blocks n bytes
Check value 4 bytes

解压缩时去掉前2个字节

var deCompressedDat = MicrosoftDecompress(compressedDat.ToArray()[2..]).AsSpan();
public static byte[] MicrosoftDecompress(byte[] data)
{
    MemoryStream compressed = new MemoryStream(data);
    MemoryStream decompressed = new MemoryStream();
    DeflateStream deflateStream = new DeflateStream(compressed, CompressionMode.Decompress);
    deflateStream.CopyTo(decompressed);
    byte[] result = decompressed.ToArray();
    return result;
}

5. 重建原始数据

PNG的IDAT数据流在压缩前会通过过滤算法将原始数据进行过滤来提高压缩率,这里需要将过滤后的数据进行重建

有关过滤和重建可以参考W3组织的文档

这里定义了一个类来辅助重建

    public class PngFilterByte
    {
        public PngFilterByte(int filterType, int row, int col)
        {
            FilterType = filterType;
            Row = row;
            Column = col;
        }

        public int Row { get; set; }

        public int Column { get; set; }

        public int FilterType { get; set; }

        public PngFilterByte C { get; set; }

        public PngFilterByte B { get; set; }

        public PngFilterByte A { get; set; }

        public int X { get; set; }

        private bool _isTop;

        public bool IsTop
        {
            get => _isTop;
            init
            {
                _isTop = value;
                if (!_isTop) return;
                B = Zero;
            }
        }

        private bool _isLeft;

        public bool IsLeft
        {
            get => _isLeft;
            init
            {
                _isLeft = value;
                if (!_isLeft) return;
                A = Zero;
            }
        }

        public int _filt;

        public int Filt
        {
            get => IsFiltered ? _filt : DoFilter();
            init
            {
                _filt = value;
            }
        }

        public bool IsFiltered { get; set; } = false;

        public int DoFilter()
        {
            _filt = FilterType switch
            {
                0 => X,
                1 => X - A.X,
                2 => X - B.X,
                3 => X - (int)Math.Floor((A.X + B.X) / 2.0M),
                4 => X - Paeth(A.X, B.X, C.X),
                _ => X
            };
            if (_filt > 255) _filt %= 256;
            IsFiltered = true;
            return _filt;
        }

        private int _recon;

        public int Recon
        {
            get => IsReconstructed ? _recon : DoReconstruction();
            init
            {
                _filt = value;
            }
        }

        public bool IsReconstructed { get; set; } = false;

        public int DoReconstruction()
        {
            _recon = FilterType switch
            {
                0 => Filt,
                1 => Filt + A.Recon,
                2 => Filt + B.Recon,
                3 => Filt + (int)Math.Floor((A.Recon + B.Recon) / 2.0M),
                4 => Filt + Paeth(A.Recon, B.Recon, C.Recon),
                _ => Filt
            };
            if (_recon > 255) _recon %= 256;
            X = _recon;
            IsReconstructed = true;
            return _recon;
        }

        private int Paeth(int a, int b, int c)
        {
            var p = a + b - c;
            var pa = Math.Abs(p - a);
            var pb = Math.Abs(p - b);
            var pc = Math.Abs(p - c);
            if (pa <= pb && pa <= pc)
            {
                return a;
            }
            else if (pb <= pc)
            {
                return b;
            }
            else
            {
                return c;
            }
        }

        public static PngFilterByte Zero = new PngFilterByte(0, -1, -1)
        {
            IsFiltered = true,
            IsReconstructed = true,
            X = 0,
            Filt = 0,
            Recon = 0
        };
    }

下面获取重建的数据

首先从IHDR获取宽高

var width = ihdrData.ReadBinaryInt(0, 4);
var height = ihdrData.ReadBinaryInt(4, 4);

按行处理

var filtRowDic = new Dictionary<int, byte[]>();
for (int i = 0; i < height; i++)
{
    var rowData = deCompressedDat.Slice(i * (width + 1), (width + 1));
    filtRowDic.Add(i, rowData.ToArray());
}

var rowColDic = new Dictionary<(int, int), PngFilterByte>();

for (int i = 0; i < height; i++)
{
    var row = filtRowDic[i];
    var filterType = row[0];
    for (int j = 1; j <= width; j++)
    {
        var bt = new PngFilterByte(filterType, i, j - 1)
        {
            Filt = Convert.ToInt32(row[j]),
            IsFiltered = true,
            IsTop = i == 0,
            IsLeft = j == 1
        };
        if (bt.IsTop && bt.IsLeft)
        {
            bt.C=PngFilterByte.Zero;
        }
        if (!bt.IsTop)
        {
            bt.B = rowColDic[(bt.Row - 1, bt.Column)];
        }

        if (!bt.IsLeft)
        {
            bt.A = rowColDic[(bt.Row, bt.Column - 1)];
        }
        rowColDic.Add((bt.Row, bt.Column), bt);
    }
}

var realImageData = new byte[rowColDic.Count];
foreach (var bt in rowColDic.Values)
{
    realImageData[bt.Row * width + bt.Column] = Convert.ToByte(bt.Recon);
}

6. 最后构建灰度Bitmap并赋予数据

using var bitmap = new Bitmap(width, height, PixelFormat.Format8bppIndexed);
ColorPalette cp = bitmap.Palette;
for (int i = 0; i < 256; i++)
{
    cp.Entries[i] = Color.FromArgb(i, i, i);
}
bitmap.Palette = cp;
var bmpData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.ReadWrite, PixelFormat.Format8bppIndexed);
Marshal.Copy(realImageData, 0, bmpData.Scan0, realImageData.Length);
bitmap.UnlockBits(bmpData);

return bitmap;

完整代码

Github Gist


参考:

1. PNG文件格式详解
2. Png的数据解析
3. How to read 8-bit PNG image as 8-bit PNG image only?
4. Portable Network Graphics (PNG) Specification (Second Edition)文章来源地址https://www.toymoban.com/news/detail-711815.html

到了这里,关于C# 手动解析灰度PNG图片为Bitmap的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 使用Python批量转换彩色图片到灰度图片

    当涉及到图像处理和计算机视觉时,有时需要将彩色图片转换为灰度图片,一张一张使用PS等工具转换十分复杂且没有必要。今天介绍的这种方法用到了Pillow库。使用Pillow库来打开,加载并转换彩色图像,并将图像储存在另一个文件夹里。具体步骤如下所示。 目录  〇、准备

    2024年02月05日
    浏览(69)
  • Android 华为手机荣耀8X调用系统裁剪工具不能裁剪方形图片,裁剪后程序就奔溃,裁剪后获取不到bitmap的问题

    买了个华为荣耀8X,安装自己写的App后,调用系统裁剪工具发现裁剪是圆形的,解决办法: 解决选择裁剪图片,每次无论怎么选,都是同一张图片的问题,解决方法如下: 在裁剪图片的方法里加上如下判断, 主要是要让return-data为false 点击确定裁剪那个对号(√)时,程序就

    2024年02月11日
    浏览(111)
  • 使用手机将图片转换成PNG格式怎么做?教你三种转换方法

    怎么使用手机把图片的格式转换成PNG格式呢?现如今的图片格式种类非常之多,有很多种格式的图片我们甚至都打不开它。有时我们需要上传一些照片文件,会有要求照片只能是PNG格式,遇到这种情况我们该怎么使用手机就能够就能将图片格式进行转换呢?其实非常简单,今

    2024年02月15日
    浏览(47)
  • png转jpg,直接改后缀?

    将PNG文件扩展名改为JPEG的扩展名(.jpg或.jpeg)不会更改图像的格式。它只是更改了文件扩展名,这可能导致一些图像查看器和编辑器无法正确识别和处理该文件。 PNG和JPEG是两种不同的图像文件格式,它们有着不同的压缩算法和特点。PNG是一种无损压缩的图像格式,可以保留

    2024年02月09日
    浏览(44)
  • C#使用OpenCv(OpenCVSharp)图像处理实例:亮度、对比度、灰度

    本文实例演示C#语言中如何使用OpenCv(OpenCVSharp)对图像进行亮度、对比度、灰度处理。 目录 亮度和对比度原理 灰度 实例 图像亮度通俗理解便是图像的明暗程度,数字图像 f(x,y) = i(x,y) r(x, y) ,如果灰度值在[0,255]之间,则 f 值越接近0亮度越低,f 值越接近255亮度越

    2024年02月13日
    浏览(69)
  • 使用nginx+HTML2canvas将任意html网页转为png图片自定义张数

    本文简述如何使用nginx+html2canvas将任意网页html转为png图片 如果是本地网页,直接进行nginx反向代理就行 如果不是本地网页,需要简单利用工具转为本地网页 导入 导入,不能使用在线的库,只能下载到本地才能导入,因为会有同源限制,否则会报跨域错误。 下载导入 由于在

    2024年01月17日
    浏览(56)
  • 解决博客不能解析PHP直接下载源码问题

    在网站设置反向代理后,网站突然不能正常访问,而是会直接下载访问文件的PHP源码 由于在搞完反向代理之后,PHP版本变成了纯静态,所以网站不能正常解析;只需要把PHP版本恢复正常即可。

    2024年02月10日
    浏览(45)
  • 【记录一次前端图片下载问题】解决跨域+直接下载

    近日有个需求需要下载协议照片,使用的是阿里云的oss,由于无法协调后端那边配置跨域响应头,找了很多方案都不理想,终于在摸索下可以实现完美下载 常见方案有两个问题 1.图片格式(png,jpg等)不会触发下载,直接打开预览 2.跨域问题 先捋一下常见方案 方法一、a标签下载

    2024年04月23日
    浏览(35)
  • C#使用OpenCvSharp4库中5个基础函数-灰度化、高斯模糊、Canny边缘检测、膨胀、腐蚀

    使用OpenCV可以对彩色原始图像进行基本的处理,涉及到5个常用的处理: 灰度化 模糊处理 Canny边缘检测 膨胀 腐蚀 本例中我们采用数字图像处理中经常用到的一副标准图像 lena.png 作为测试图像,如下图所示: 具体资源下载地址为:lena图像下载地址 首先我们新建一个基于C# .

    2024年04月22日
    浏览(52)
  • C# Bitmap类学习1

    Bitmap对象封装了GDI+中的一个位图,此位图由图形图像及其属性的像素数据组成.因此Bitmap是用于处理由像素数据定义的图像的对象。 先生成一个100*100大小的位图bmp1; 然后从bmp1获取Graphics对象g,然后用g进行绘制,这样绘制的东西是绘制在bmp1上;绘制字符串,绘制矩形; 然后

    2024年01月25日
    浏览(40)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包