0

如何使用 C# 压缩具有运行长度编码的图像?是否有任何可用的库来支持这一点?

游程编码是否仅适用于位图图像?如果是这样,我将如何使用 C# 将图像类型转换为位图?

我还想问一下他们在此之后生成的文件类型是什么,他们会保留他们的文件类型还是会有一个新的文件类型?

4

3 回答 3

2

我知道这是一个老问题,但它是谷歌搜索中 C# 中 RLE 压缩的少数几个问题之一。对于像我这样完全没有图像处理经验的人来说,这是一次非常令人沮丧的经历,所以希望这个答案能在将来减轻其他人的痛苦。

我知道 RLE 是一种过时的压缩形式,但我们中的一些人受到要求我们使用它的约束(例如将 bmp 导出到自定义设备)。以下需要 8bpp 索引位图才能工作。如果您需要帮助将图像转换为该格式,有大量在线资源可以提供帮助。

我把它放在一个名为 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();
    }

希望这可以帮助!

于 2014-11-03T15:31:45.960 回答
0

如果你环顾四周,你会发现相当多的资源......

运行长度编码几乎适用于字符串形式的任何类型的数据。

但这里有一个来自Rosetta Code的例子:

   public static void Main(string[] args)
   {
       string input = "WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW";
       Console.WriteLine(Encode(input));//Outputs: 12W1B12W3B24W1B14W
       Console.WriteLine(Decode(Encode(input)));//Outputs: WWWWWWWWWWWWBWWWWWWWWWWWWBBBWWWWWWWWWWWWWWWWWWWWWWWWBWWWWWWWWWWWWWW
       Console.ReadLine();
   }
   public static string Encode(string s)
   {
       StringBuilder sb = new StringBuilder();
       int count = 1;
       char current =s[0];
       for(int i = 1; i < s.Length;i++)
       {
           if (current == s[i])
           {
               count++;
           }
           else
           {
               sb.AppendFormat("{0}{1}", count, current);
               count = 1;
               current = s[i];
           }
       }
       sb.AppendFormat("{0}{1}", count, current);
       return sb.ToString();
   }
   public static string Decode(string s)
   {
       string a = "";
       int count = 0;
       StringBuilder sb = new StringBuilder();
       char current = char.MinValue;
       for(int i = 0; i < s.Length; i++)
       {
           current = s[i];
           if (char.IsDigit(current))
               a += current;
           else
           {
               count = int.Parse(a);
               a = "";
               for (int j = 0; j < count; j++)
                   sb.Append(current);
           }
       }
       return sb.ToString();
   }

您必须根据自己的需要来调整它,但它应该可以帮助您入门。

于 2012-10-10T14:58:05.940 回答
-3

为了压缩图像,我编写了一些函数:这将调整宽度并通过纵横比确定高度。请注意,如果图像的原始宽度小于新宽度,则图像未调整大小(您绝对可以在代码中更改)。

    public Bitmap ResizedImage(Stream imageStream, int newWidth)
    {
        Image image = Image.FromStream(imageStream);
        int newwidthimg = image.Size.Width;
        int newheightimg = image.Size.Height;
        if (newWidth <= image.Size.Width)
        {
            newwidthimg = newWidth;
            float AspectRatio = (float)image.Size.Width / (float)image.Size.Height;
            newheightimg = Convert.ToInt32(newwidthimg / AspectRatio);
        }
        Bitmap thumbnailBitmap = new Bitmap(newwidthimg, newheightimg);
        using (Graphics thumbnailGraph = Graphics.FromImage(thumbnailBitmap))
        {
            thumbnailGraph.CompositingQuality = CompositingQuality.HighQuality;
            thumbnailGraph.SmoothingMode = SmoothingMode.HighQuality;
            thumbnailGraph.InterpolationMode = InterpolationMode.HighQualityBicubic;
            var imageRectangle = new Rectangle(0, 0, newwidthimg, newheightimg);
            thumbnailGraph.DrawImage(image, imageRectangle);
        }
        return thumbnailBitmap;
于 2012-10-10T14:59:40.067 回答