如何使用C#压缩使用运行长度编码的图像? [英] How do I compress an image with Run-Length Encoding using C#?

查看:191
本文介绍了如何使用C#压缩使用运行长度编码的图像?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

如何使用C#压缩使用运行长度编码的图像?是否有任何可用的库来支持此操作?

How do I compress an image with Run-Length Encoding using C#? Are there any available libraries to support this?

运行长度编码只适用于位图图像?如果是这样,我将如何使用C#将图像类型转换为位图?

Does Run-Length Encoding only work on bitmapped images? If so, How will I convert image types to bitmap using C#?

我也想问他们的结果文件类型之后,他们会保留他们的文件类型或者他们会有新的吗?

I'd also like to ask what's their resulting file type after this, will they retain their file type or will they have a new one?

推荐答案

我知道这是一个老问题,在Google搜索的C#中用于RLE压缩。对于像我这样没有图像处理经验的人来说,这是一个非常令人沮丧的体验,所以希望这个答案能够在将来节省一些其他的痛苦。

I know this is an old question, but it is one of the few things that comes up for RLE compression in C# on Google search. For someone like me, who has no experience with image processing at all, this was a very frustrating experience, so hopefully this answer will save some others pain in the future.

我理解RLE是一种过时的压缩形式,但是我们中的一些人仍然遇到需要我们使用它的约束(例如将bmp导出到自定义设备)。以下需要8bpp索引的位图才能工作。如果你需要帮助将图像转换为该格式,有很多资源在线帮助。

I understand that RLE is an out-dated form of compression, but some of us are stuck with constraints (such as exporting a bmp to a custom device) that require us to use it. The following requires an 8bpp indexed bitmap in order to work. If you need help converting an image to that format, there are a good number of resources online to help out.

我把它放在一个名为BmpCompressor的类中。

I put this inside a class called BmpCompressor.

private enum Compression
    {
        // others not necessary for the 8bpp compression, but left for reference
        //BI_RGB = 0x0000,
        BI_RLE8 = 0x0001,
        //BI_RLE4 = 0x0002,
        //BI_BITFIELDS = 0x0003,
        //BI_JPEG = 0x0004,
        //BI_PNG = 0x0005,
        //BI_CMYK = 0x000B,
        //BI_CMYKRLE8 = 0x000C,
        //BI_CMYKRLE4 = 0x000D
    }

    private enum BitCount
    {
        // others not necessary for the 8bpp compression, but left for reference
        //Undefined = (ushort)0x0000,
        //TwoColors = (ushort)0x0001,
        //Max16Colors = (ushort)0x0004,
        Max256Colors = (ushort)0x0008,
        //Max32KBColors = (ushort)0x0010,
        //Max16MBColors = (ushort)0x0018,
        //Max16MBColors_Compressed = (ushort)0x0020
    }

    private struct RleCompressedBmpHeader
    {
        // Everything before the HeaderSize is technically not part of the header (it's not included in the HeaderSize calculation)

        /// <summary>
        /// Size of the .bmp file.
        /// Always header size (40), plus palette size, plus image size, plus pre-header size (14);
        /// </summary>
        public uint Size;

        /// <summary>
        /// Offset to start of image data in bytes from the start of the file
        /// </summary>
        public uint Offset;

        /// <summary>
        /// Size of this header in bytes. (Always 40)
        /// </summary>
        public uint HeaderSize; // 4 + 4 + 4 + 2 + 2 + 4 + 4 + 4 + 4 + 4 + 4

        /// <summary>
        /// Width of bitmap in pixels
        /// </summary>
        public int Width;

        /// <summary>
        /// Height of bitmap in pixels
        /// </summary>
        public int Height;

        /// <summary>
        /// Number of Planes (layers). Always 1.
        /// </summary>
        public ushort Planes;
        /// <summary>
        /// Number of bits that define each pixel and maximum number of colors
        /// </summary>
        public BitCount BitCount;

        /// <summary>
        /// Defines the compression mode of the bitmap.
        /// </summary>
        public Compression Compression;

        /// <summary>
        /// Size, in bytes, of image.
        /// </summary>
        public uint ImageSize;

        // These shouldn't really be all that important
        public uint XPixelsPerMeter;
        public uint YPixelsPerMeter;

        /// <summary>
        /// The number of indexes in the color table used by this bitmap.
        /// <para>0 - Use max available</para>
        /// <para>If BitCount is less than 16, this is the number of colors used by the bitmap</para>
        /// <para>If BitCount is 16 or greater, this specifies the size of the color table used to optimize performance of the system palette.</para>
        /// </summary>
        public uint ColorUsed;

        /// <summary>
        /// Number of color indexes that are required for displaying the bitmap. 0 means all color indexes are required.
        /// </summary>
        public uint ColorImportant;

        public byte[] ToBytes()
        {
            var swap = BitConverter.IsLittleEndian;
            var result = new List<byte>();

            result.AddRange(new byte[] { 0x42, 0x4d }); // signature (BM)
            result.AddRange(BitConverter.GetBytes(Size));
            result.AddRange(new byte[4]); // reserved
            result.AddRange(BitConverter.GetBytes(Offset));
            result.AddRange(BitConverter.GetBytes(HeaderSize));
            result.AddRange(BitConverter.GetBytes(Width));
            result.AddRange(BitConverter.GetBytes(Height));
            result.AddRange(BitConverter.GetBytes(Planes));
            result.AddRange(BitConverter.GetBytes((ushort)BitCount));
            result.AddRange(BitConverter.GetBytes((uint)Compression));
            result.AddRange(BitConverter.GetBytes(ImageSize));
            result.AddRange(BitConverter.GetBytes(XPixelsPerMeter));
            result.AddRange(BitConverter.GetBytes(YPixelsPerMeter));
            result.AddRange(BitConverter.GetBytes(ColorUsed));
            result.AddRange(BitConverter.GetBytes(ColorImportant));

            return result.ToArray();
        }
    }

    public unsafe byte[] RunLengthEncodeBitmap(Bitmap bmp)
    {
        if (bmp.PixelFormat != PixelFormat.Format8bppIndexed) { throw new ArgumentException("The image must be in 8bppIndexed PixelFormat", "bmp"); }

        var data = bmp.LockBits(new Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadOnly, PixelFormat.Format8bppIndexed);
        List<byte> result = new List<byte>();

        // Actual RLE algorithm. Bottom of image is first stored row, so start from bottom.
        for (var rowIndex = bmp.Height - 1; rowIndex >= 0; rowIndex--)
        {
            byte? storedPixel = null;
            var curPixelRepititions = 0;
            var imageRow = (byte*)data.Scan0.ToPointer() + (rowIndex * data.Stride);
            for (var pixelIndex = 0; pixelIndex < bmp.Width; pixelIndex++)
            {
                var curPixel = imageRow[pixelIndex];
                if (!storedPixel.HasValue)
                {
                    curPixelRepititions = 1;
                    storedPixel = curPixel;
                }
                else if (storedPixel.Value != curPixel || curPixelRepititions == 255)
                {
                    result.Add(Convert.ToByte(curPixelRepititions));
                    result.Add(storedPixel.Value);
                    curPixelRepititions = 1;
                    storedPixel = curPixel;
                }
                else
                {
                    curPixelRepititions++;
                }
            }

            if (curPixelRepititions > 0)
            {
                result.Add(Convert.ToByte(curPixelRepititions));
                result.Add(storedPixel.Value);
            }

            if (rowIndex == 0)
            {
                // EOF flag
                result.Add(0x00);
                result.Add(0x01);
            }
            else
            {
                // End of Line Flag
                result.Add(0x00);
                result.Add(0x00);
            }
        }

        bmp.UnlockBits(data);

        var paletteSize = (uint)bmp.Palette.Entries.Length * 4;
        var header = new RleCompressedBmpHeader();
        header.HeaderSize = 40;
        header.Size = header.HeaderSize + paletteSize + (uint)result.Count + 14;
        header.Offset = header.HeaderSize + 14 + paletteSize; // total header size + palette size
        header.Width = bmp.Width;
        header.Height = bmp.Height;
        header.Planes = 1;
        header.BitCount = BitCount.Max256Colors;
        // as far as I can tell, PixelsPerMeter are not terribly important
        header.XPixelsPerMeter = 0x10000000;
        header.YPixelsPerMeter = 0x10000000;
        header.Compression = Compression.BI_RLE8;
        header.ColorUsed = 256;
        header.ColorImportant = 0; // use all available colors
        header.ImageSize = header.HeaderSize + (uint)result.Count;

        var headerBytes = header.ToBytes();
        var paletteBytes = ConvertPaletteToBytes(bmp.Palette);

        return headerBytes.Concat(paletteBytes).Concat(result).ToArray();
    }

    private byte[] ConvertPaletteToBytes(ColorPalette colorPalette)
    {
        return colorPalette.Entries.SelectMany(c => new byte[]
            {
                c.B,
                c.G,
                c.R,
                0
            }).ToArray();
    }

希望这有助!

这篇关于如何使用C#压缩使用运行长度编码的图像?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆