C#编码位图到DDS(DXT1,DXT3,DXT5)

发布于 2025-01-24 12:39:48 字数 6895 浏览 2 评论 0原文

我的应用程序中有一个简单的位图。我需要将其保存为DDS(直接绘制的表面)不像带有.DDS扩展名的简单映像,而需要编码像素数据并将其写入.bin文件。 因此,关于MSDN文档( dds dds_header结构 a href =“ https://learn.microsoft.com/en-us/windows/windows/win32/direct3ddds/dds-pixelformat” rel =“ nofollow noreferrer”> dds_pixelformat结构像素格式和编码像素数据(主表面,然后是其他表面 - 不是吗?)。好的,所以我要做的事情:

private const uint DDS_SIGNATURE = 0x20534444;
private const uint DDPF_FOURCC = 0x00000004;

private const int DDSD_CAPS = 0x00000001;
private const int DDSD_HEIGHT = 0x00000002;
private const int DDSD_WIDTH = 0x00000004;
private const int DDSD_PITCH = 0x00000008;
private const int DDSD_PIXELFORMAT = 0x00001000;
private const int DDSD_MIPMAPCOUNT = 0x00020000;
private const int DDSD_LINEARSIZE = 0x00080000;

private const int DDSCAPS_COMPLEX = 0x00000008;
private const int DDSCAPS_TEXTURE = 0x00001000;
private const int DDSCAPS_MIPMAP = 0x00400000;

private const uint FOURCC_DXT1 = 0x31545844;
private const uint FOURCC_DXT2 = 0x32545844;
private const uint FOURCC_DXT3 = 0x33545844;
private const uint FOURCC_DXT4 = 0x34545844;
private const uint FOURCC_DXT5 = 0x35545844;

private static byte[] GetDDSImageBytes(Bitmap image, EDDSCompressionMode format)
{
    if (image is null ||
        (format != EDDSCompressionMode.DXT1
            && format != EDDSCompressionMode.DXT2
            && format != EDDSCompressionMode.DXT3
            && format != EDDSCompressionMode.DXT4
            && format != EDDSCompressionMode.DXT5))
    {
        return null;
    }
    
    var capsMipMap = 0;
    var capsComplex = 0;
    var mipMapsCount = 0;
    var mipMaps = new List<Bitmap>();

    if (format == EDDSCompressionMode.DXT2 || format == EDDSCompressionMode.DXT3) // let's imagine I need MipMaps only for these formats
    {
        capsMipMap = DDSCAPS_MIPMAP;
        capsComplex = DDSCAPS_COMPLEX;
        mipMapsCount = DDSD_MIPMAPCOUNT;

        while (true)
        {
            var width = Math.Max(1, image.Width >> 1);
            var height = Math.Max(1, image.Height >> 1);

            if (width == 1 && height == 1)
            {
                break;
            }

            mipMaps.Add(CreateMipMap(image, width, height));
        }
    }

    var header = new DDSHeader
    {
        Size = 124,
        Flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | mipMapsCount | DDSD_LINEARSIZE,
        Height = image.Height,
        Width = image.Width,
        PitchOrLinearSize = image.Width * image.Height, // I'm not sure it's a proper calculation, please correct me if I'm wrong
        Depth = 0,
        MipMapCount = mipMaps.Count,
        Caps = DDSCAPS_TEXTURE | capsMipMap | capsComplex,
        Caps2 = 0,
        Caps3 = 0,
        Caps4 = 0,
        Reserved2 = 0,
    };

    var pixelFormat = new DDSPixelFormat
    {
        Size = 32,
        Flags = DDPF_FOURCC,
        FourCC = format switch
        {
            EDDSCompressionMode.DXT1 => FOURCC_DXT1,
            EDDSCompressionMode.DXT2 => FOURCC_DXT2,
            EDDSCompressionMode.DXT3 => FOURCC_DXT3,
            EDDSCompressionMode.DXT4 => FOURCC_DXT4,
            EDDSCompressionMode.DXT5 or _ => FOURCC_DXT5,
        },
        RGBBitCount = 0,
        RBitMask = 0,
        GBitMask = 0,
        BBitMask = 0,
        ABitMask = 0
    };

    var data = new List<byte>(CreateHeaderAndFormat(header, pixelFormat));

    //TODO: Implement writing binary data

    return data.ToArray();
}

private static Bitmap CreateMipMap(Bitmap image, int width, int height)
{
    var bmp = new Bitmap(width, height);

    using var blitter = Graphics.FromImage(bmp);

    blitter.InterpolationMode = InterpolationMode.HighQualityBicubic;

    using var wrapMode = new ImageAttributes();

    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
    blitter.DrawImage(image, new Rectangle(0, 0, width, height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);

    return bmp;
}

private static byte[] CreateHeaderAndFormat(DDSHeader header, DDSPixelFormat pixelFormat)
{
    var data = new List<byte>(BitConverter.GetBytes(DDS_SIGNATURE));

    data.AddRange(BitConverter.GetBytes(header.Size));
    data.AddRange(BitConverter.GetBytes(header.Flags));
    data.AddRange(BitConverter.GetBytes(header.Height));
    data.AddRange(BitConverter.GetBytes(header.Width));
    data.AddRange(BitConverter.GetBytes(header.PitchOrLinearSize));
    data.AddRange(BitConverter.GetBytes(header.Depth));
    data.AddRange(BitConverter.GetBytes(header.MipMapCount));

    for (var i = 0; i < 11; i++)
    {
        data.AddRange(BitConverter.GetBytes((uint)0));
    }

    data.AddRange(BitConverter.GetBytes(pixelFormat.Size));
    data.AddRange(BitConverter.GetBytes(pixelFormat.Flags));
    data.AddRange(BitConverter.GetBytes(pixelFormat.FourCC));
    data.AddRange(BitConverter.GetBytes(pixelFormat.RGBBitCount));
    data.AddRange(BitConverter.GetBytes(pixelFormat.RBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.GBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.BBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.ABitMask));

    data.AddRange(BitConverter.GetBytes(header.Caps));
    data.AddRange(BitConverter.GetBytes(header.Caps2));
    data.AddRange(BitConverter.GetBytes(header.Caps3));
    data.AddRange(BitConverter.GetBytes(header.Caps4));
    data.AddRange(BitConverter.GetBytes(header.Reserved2));

    return data.ToArray();
}

一些其他信息:

public enum EDDSCompressionMode
{
    Unknown = 0,
    DXT1 = 1,
    DXT2 = 2,
    DXT3 = 3,
    DXT4 = 4,
    DXT5 = 5
}

public struct DDSHeader
{
    public int Size;
    public int Flags;
    public int Height;
    public int Width;
    public int PitchOrLinearSize;
    public int Depth;
    public int MipMapCount;
    public int[] Reserved1;
    public int Caps;
    public int Caps2;
    public int Caps3;
    public int Caps4;
    public int Reserved2;
}

public struct DDSPixelFormat
{
    public uint Size;
    public uint Flags;
    public uint FourCC;
    public uint RGBBitCount;
    public uint RBitMask;
    public uint GBitMask;
    public uint BBitMask;
    public uint ABitMask;
}

问题是我不是DDS图像的专家,我不知道如何从我的位图创建适当的二进制数据。寻找一些外部库,但似乎所有这些库都指示保存.DDS文件,而不是返回像素的编码二进制数据。

我发现的另一件事是实现从文件中读取数据(并且它正常工作!):<​​a href =“ https://github.com/xackery/eqzip/eqzip/blob/blob/master/master/ddsimage.cs” rel =' nofollow noreferrer“> eqzip 。 似乎我只能从上面的链接中重写(IE Invert)DXT1,DXT3和DXT5方法以获取编码的数据,但是再次 - 我不知道该如何正确执行。

有什么建议,想法,建议,技巧等吗?

I have a simple Bitmap in my app. I need to save it as DDS (Direct Draw Surface) not like a simple image with .dds extension, but need to encode pixel data and write it to .bin file.
So, regarding to MSDN Documentation (Programming Guide for DDS,DDS_HEADER structure, DDS_PIXELFORMAT structure) DDS images consists from signature, header, pixel format and encoded pixels data (main surface and then other surfaces - isn't it?). Ok, so what I'm trying to do:

private const uint DDS_SIGNATURE = 0x20534444;
private const uint DDPF_FOURCC = 0x00000004;

private const int DDSD_CAPS = 0x00000001;
private const int DDSD_HEIGHT = 0x00000002;
private const int DDSD_WIDTH = 0x00000004;
private const int DDSD_PITCH = 0x00000008;
private const int DDSD_PIXELFORMAT = 0x00001000;
private const int DDSD_MIPMAPCOUNT = 0x00020000;
private const int DDSD_LINEARSIZE = 0x00080000;

private const int DDSCAPS_COMPLEX = 0x00000008;
private const int DDSCAPS_TEXTURE = 0x00001000;
private const int DDSCAPS_MIPMAP = 0x00400000;

private const uint FOURCC_DXT1 = 0x31545844;
private const uint FOURCC_DXT2 = 0x32545844;
private const uint FOURCC_DXT3 = 0x33545844;
private const uint FOURCC_DXT4 = 0x34545844;
private const uint FOURCC_DXT5 = 0x35545844;

private static byte[] GetDDSImageBytes(Bitmap image, EDDSCompressionMode format)
{
    if (image is null ||
        (format != EDDSCompressionMode.DXT1
            && format != EDDSCompressionMode.DXT2
            && format != EDDSCompressionMode.DXT3
            && format != EDDSCompressionMode.DXT4
            && format != EDDSCompressionMode.DXT5))
    {
        return null;
    }
    
    var capsMipMap = 0;
    var capsComplex = 0;
    var mipMapsCount = 0;
    var mipMaps = new List<Bitmap>();

    if (format == EDDSCompressionMode.DXT2 || format == EDDSCompressionMode.DXT3) // let's imagine I need MipMaps only for these formats
    {
        capsMipMap = DDSCAPS_MIPMAP;
        capsComplex = DDSCAPS_COMPLEX;
        mipMapsCount = DDSD_MIPMAPCOUNT;

        while (true)
        {
            var width = Math.Max(1, image.Width >> 1);
            var height = Math.Max(1, image.Height >> 1);

            if (width == 1 && height == 1)
            {
                break;
            }

            mipMaps.Add(CreateMipMap(image, width, height));
        }
    }

    var header = new DDSHeader
    {
        Size = 124,
        Flags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | mipMapsCount | DDSD_LINEARSIZE,
        Height = image.Height,
        Width = image.Width,
        PitchOrLinearSize = image.Width * image.Height, // I'm not sure it's a proper calculation, please correct me if I'm wrong
        Depth = 0,
        MipMapCount = mipMaps.Count,
        Caps = DDSCAPS_TEXTURE | capsMipMap | capsComplex,
        Caps2 = 0,
        Caps3 = 0,
        Caps4 = 0,
        Reserved2 = 0,
    };

    var pixelFormat = new DDSPixelFormat
    {
        Size = 32,
        Flags = DDPF_FOURCC,
        FourCC = format switch
        {
            EDDSCompressionMode.DXT1 => FOURCC_DXT1,
            EDDSCompressionMode.DXT2 => FOURCC_DXT2,
            EDDSCompressionMode.DXT3 => FOURCC_DXT3,
            EDDSCompressionMode.DXT4 => FOURCC_DXT4,
            EDDSCompressionMode.DXT5 or _ => FOURCC_DXT5,
        },
        RGBBitCount = 0,
        RBitMask = 0,
        GBitMask = 0,
        BBitMask = 0,
        ABitMask = 0
    };

    var data = new List<byte>(CreateHeaderAndFormat(header, pixelFormat));

    //TODO: Implement writing binary data

    return data.ToArray();
}

private static Bitmap CreateMipMap(Bitmap image, int width, int height)
{
    var bmp = new Bitmap(width, height);

    using var blitter = Graphics.FromImage(bmp);

    blitter.InterpolationMode = InterpolationMode.HighQualityBicubic;

    using var wrapMode = new ImageAttributes();

    wrapMode.SetWrapMode(WrapMode.TileFlipXY);
    blitter.DrawImage(image, new Rectangle(0, 0, width, height), 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode);

    return bmp;
}

private static byte[] CreateHeaderAndFormat(DDSHeader header, DDSPixelFormat pixelFormat)
{
    var data = new List<byte>(BitConverter.GetBytes(DDS_SIGNATURE));

    data.AddRange(BitConverter.GetBytes(header.Size));
    data.AddRange(BitConverter.GetBytes(header.Flags));
    data.AddRange(BitConverter.GetBytes(header.Height));
    data.AddRange(BitConverter.GetBytes(header.Width));
    data.AddRange(BitConverter.GetBytes(header.PitchOrLinearSize));
    data.AddRange(BitConverter.GetBytes(header.Depth));
    data.AddRange(BitConverter.GetBytes(header.MipMapCount));

    for (var i = 0; i < 11; i++)
    {
        data.AddRange(BitConverter.GetBytes((uint)0));
    }

    data.AddRange(BitConverter.GetBytes(pixelFormat.Size));
    data.AddRange(BitConverter.GetBytes(pixelFormat.Flags));
    data.AddRange(BitConverter.GetBytes(pixelFormat.FourCC));
    data.AddRange(BitConverter.GetBytes(pixelFormat.RGBBitCount));
    data.AddRange(BitConverter.GetBytes(pixelFormat.RBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.GBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.BBitMask));
    data.AddRange(BitConverter.GetBytes(pixelFormat.ABitMask));

    data.AddRange(BitConverter.GetBytes(header.Caps));
    data.AddRange(BitConverter.GetBytes(header.Caps2));
    data.AddRange(BitConverter.GetBytes(header.Caps3));
    data.AddRange(BitConverter.GetBytes(header.Caps4));
    data.AddRange(BitConverter.GetBytes(header.Reserved2));

    return data.ToArray();
}

Some additional info:

public enum EDDSCompressionMode
{
    Unknown = 0,
    DXT1 = 1,
    DXT2 = 2,
    DXT3 = 3,
    DXT4 = 4,
    DXT5 = 5
}

public struct DDSHeader
{
    public int Size;
    public int Flags;
    public int Height;
    public int Width;
    public int PitchOrLinearSize;
    public int Depth;
    public int MipMapCount;
    public int[] Reserved1;
    public int Caps;
    public int Caps2;
    public int Caps3;
    public int Caps4;
    public int Reserved2;
}

public struct DDSPixelFormat
{
    public uint Size;
    public uint Flags;
    public uint FourCC;
    public uint RGBBitCount;
    public uint RBitMask;
    public uint GBitMask;
    public uint BBitMask;
    public uint ABitMask;
}

So the problem is that I'm not an expert regarding DDS images and I have no idea how to create a proper binary data from my Bitmap. Looked for some external libraries, but seems like all of them directed to save .dds file, not to return encoded binary data of pixels.

Another thing which I found is implementation of reading the data from the file (and it's working properly!): EQZip.
Seems like I can only rewrite (i.e. invert) DXT1, DXT3 and DXT5 methods from link above to get the encoded data, but again - I have no idea how to do it properly.

Any suggestions, ideas, advices, tips, etc.?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文