图像处理(Image Processing) ---------- PCX Decoding (C#实现) )

版权声明:此为个人学习与研究成果,若需转载请提前告知。 https://blog.csdn.net/weixin_35811044/article/details/83997166
  • PCX是一种古老的 图像格式,现在已经被jpeg、png、gif等新生格式替代。但是作为图像处理新手的话,把它拿来练练手还很好的选择。
  • PCX文档,由头文档、位图数据 (bitmap)、256色调色盘三部分构成。压缩采用的是RLE(Run Length Encoding)编码法,bitmap数据存的是已经压缩过的数据,且属于无损压缩。要读PCX文档就要进行RLE解码。
  • 头文档:共有 128 bytes,要解码PCX,解读头文档非常重要。
    • PCX头文件,最重要的是解读这几个部分:
    1. 版本号,若为5,通常在文件最末尾都有一个256色的调色盘,即文件的最后的768 bytes为调色盘数据,按顺序以R/G/B,每三个bytes构成一个调色盘的颜色,通常倒数的第769个bytes会是一个鉴定位,值一般为12,表示后面有256色调色盘。另外pcx档是支持24位全真彩的。
    2. 像素位 (第3个字节) 和 彩色平面数 (第65个字节) ,决定了图像的类别。如色彩平面数大于1 ,那就没有使用调色盘。例如:像素位:8 ,平面数:1 ,为256色图像,使用了末尾调色盘。像素位:8 ,平面数:3 ,为24为全彩图像,没有使用调试盘。
    3. 图像大小(第4个字节),可以得到整个图像的像素个数还有宽、高。要注意的是第一个像素是(0,0),所以算真实个数(宽、高)时:(Xmax - Xmin)+1,(Ymax - Ymin)+1。 

 

起始字节

字节数

    内容

                                  解释

0

1

Zsoft标志

为10(0x0a),即为Zsoft PCX文件

1

1

版本号

0:PC Paintbrush 2.51,带调色板。 5:支持24位真彩.

2

1

编码

1:RLE编码法

3

1

位/像素

每个平面的像素有几位,可能值为1、2,、4或8

4

8

图像大小

图像边界极限,一边两字节,按顺序为:

Xmin、Ymin、Xmax、Ymax,以像素为单位。

12

2

水平分辨率

打印时,X方向的每英寸像素点的点数

14

2

垂直分辨率

打印时,Y方向的每英寸像素点的点数

16

48

文件头调色板

16色的“EGA/VGA”头调色板

64

1

保留字节

Zsoft保留,为0

65

1

平面

彩色平面数。PCX图像可以是单彩色,也可以具有多个彩色平面,平面数可为:1、2、3、4

66

2

每行字节数

每个色彩平面的每行字节数,即解压后图像每一行所占的字节数,总是偶数。

68

2

调色板解释

1:彩色

2:灰度

70

2

视频屏幕大小X

视频输出的水平像素数-1

72

2

视频屏幕大小Y

视频输出的垂直像素数-1

74

54

全空直到文件结束

  • 位图数据:
    • 解读完PCX的头文件后,我们就可以根据头文件的一些信息,进一步解读位图数据,从而读出整张图片。对于PCX文档的位图数据使用了RLE编码法压缩,在coding之前,先了解一下如何解RLE压缩。
    • PCX中使用的RLE原理:以像素位为8bit图案为例。
    1. 在压缩的PCX位图数据中,分为两种数据:一种是记录数据(某一pixel值的个数),一种是真实数据(pixel值)。
    2. PCX的位图数据里,每个数据8个bit: a)如果最高两位为11,此数据为记录数据,记录的是下一个数据作为pixel值将在原图中连续出现的次数(与192的差值)。ex:一个数据11000011(195) 比11000000(192)大3 --> 即下一个数据的值是原图中连续3个的pixel值。b)如果最高位不是11,则此数据为真实数据,就是原图对应的pixel值。ex:01000000(64),原图对应pixel值就为64。 
    3. 如果原图中的pixel值本身就大于192,即高位也为11,那么在读压缩数据时会无法判断此数据是记录数据还是真实数据。解决办法是,原图中如果大于192的pixel值就算只有1个,在压缩数据中也要用一个记录数据去记录它。这样不管如何,读到高位为11的数据,皆为记录数据
    • 如下图:

假设在压缩的PCX位图数据中,一段数据为:

 那么这一段数据,解压后的原图pixel数据为:

  • C#实作:读取两类pcx图档。
    1.  one plane & 8 bits/pixel
      1. 使用调色盘,256色图。
      2. pixel值表示调色盘索引,指向调色盘。
    2.  three planes & 8 bits/pixel
      1. 未使用调色盘,全彩图。
      2. 位图数据有序的按原图每一行记录,先记录每行所有R值、再记录G值、最后记录B值,换行时可能有0作为换行指标。
      3. 在C#的bitmap类中,RGB数组中存储顺序为[2][1][0]。

代码:完整读取两类PCX图档,包括读头文件、读位图数据、读尾部调色盘。

using System;
using System.IO;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;

namespace PCXDecodeClass
{
    class readHeader
    {
        byte[] imageHeader = new byte[128];

        public readHeader() { }
          /*
            读PCX图像头部
         */
        public readHeader(byte[] FileData)
        {
            Array.Copy(FileData, imageHeader, 128);
        }

        public readHeader(String FilePath)
        {
            byte[] imageFile = File.ReadAllBytes(FilePath);
            Array.Copy(imageFile, imageHeader, 128);
        }

        //厂商必须是10
        public byte Manufacturer { get { return imageHeader[0]; } }
        //版本,5代表后面有256色的调色盘
        public byte Version { get { return imageHeader[1]; } }
        //编码方式,1为RLE压缩法
        public byte Encoding { get { return imageHeader[2]; } }
        //每个平面的每个像素有几位
        public byte BitsPerPixel { get { return imageHeader[3]; } }
        //图像边界 通常(0,0)开始到(X,X),以像素为单位,真实长宽记得+1.
        public ushort Xmin { get { return BitConverter.ToUInt16(imageHeader, 4); } }
        public ushort Ymin { get { return BitConverter.ToUInt16(imageHeader, 6); } }
        public ushort Xmax { get { return BitConverter.ToUInt16(imageHeader, 8); } }
        public ushort Ymax { get { return BitConverter.ToUInt16(imageHeader, 10); } }
        //水平分辨率,每英寸点数
        public ushort Hres { get { return BitConverter.ToUInt16(imageHeader, 12); } }
        //竖直分辨率,每英寸点数
        public ushort Vres { get { return BitConverter.ToUInt16(imageHeader, 14); } }
        //默认调色盘
        public byte[] Palette { get { byte[] palette = new byte[48]; Array.Copy(imageHeader, 16, palette, 0, 48); return palette; } }
        //保留位
        public byte Reserve { get { return imageHeader[64]; } }
        //颜色平面数
        public byte ColorPlanes { get { return imageHeader[65]; } }
        //解压后,图片每行byte数
        public ushort BytesPerLine { get { return BitConverter.ToUInt16(imageHeader, 66); } }
        //调色盘类别,1:彩色或黑白,2:灰度
        public ushort PaletteType { get { return BitConverter.ToUInt16(imageHeader, 68); } }
        //空余空间
        public byte[] Filled
        {
            get { byte[] filled = new byte[58]; Array.Copy(imageHeader, 70, filled, 0, 58); return filled; }
        }
        public int Width { get { return Xmax - Xmin + 1; } }

        public int Height { get { return Ymax - Ymin + 1; } }
    }

    /*
        读PCX图像内容 
     */
    class readMidResource
    {
        public readMidResource(String FilePath)
        {
            if (File.Exists(FilePath)) { DecoPcx(File.ReadAllBytes(FilePath)); } else { return; }
        }

        private Bitmap DecoImage;
        public Bitmap getDecoImage { get { return DecoImage; } }

        readHeader FileHead;
        int readIndex;

        private void DecoPcx(byte[] FileBytes)
        {
            readIndex = 128;
            FileHead = new readHeader(FileBytes);
            if (FileHead.Manufacturer != 10) return;
            PixelFormat pixelFormat = PixelFormat.Format24bppRgb;

            DecoImage = new Bitmap(FileHead.Width, FileHead.Height, pixelFormat);
           
            if (FileHead.ColorPlanes == 3 && FileHead.BitsPerPixel == 8)
            {
                BitmapData DecoImageData = DecoImage.LockBits(new Rectangle(0, 0, FileHead.Width, FileHead.Height), ImageLockMode.ReadWrite, pixelFormat);
                byte[] RGBData = new byte[DecoImageData.Height * DecoImageData.Stride];
                byte[] RowRGBData = new byte[0];
                for (int i = 0; i < DecoImageData.Height; i++)
                {
                    RowRGBData = decode24(FileBytes);
                    Array.Copy(RowRGBData, 0, RGBData, i * DecoImageData.Stride, RowRGBData.Length);
                }
                Marshal.Copy(RGBData, 0, DecoImageData.Scan0, RGBData.Length);
                DecoImage.UnlockBits(DecoImageData);
            }
            else if (FileHead.ColorPlanes == 1 && FileHead.BitsPerPixel == 8)
            {
                    DecoImage = decode8(FileBytes);
            }
        }

        //解析8位256色圖像
        private Bitmap decode8(byte[] FileBytes)
        {
            
            byte[] AllPixelData = new byte[FileHead.Height * FileHead.Width];
            int EndIndex = FileBytes.Length - 769;
            int HaveWriteTo = 0;
            readTailPalette rtp = new readTailPalette(FileBytes);
            Color[] palette = rtp.getPalette();
            Bitmap Image24bit = new Bitmap(FileHead.Width, FileHead.Height, PixelFormat.Format24bppRgb);
            int index = 0;

            while (true)
            {
                if (readIndex >= EndIndex) break;
                byte ByteValue = FileBytes[readIndex];
                if (ByteValue > 0xC0)
                {
                    int Count = ByteValue - 0xC0;
                    readIndex++;
                    for (int i = 0; i < Count; i++)
                    {
                        int RGBIndex = i + HaveWriteTo;
                        AllPixelData[RGBIndex] = FileBytes[readIndex];
                    }
                    HaveWriteTo += Count;
                    readIndex++;
                }
                else
                {
                    int RGBIndex = HaveWriteTo;
                    AllPixelData[RGBIndex] = ByteValue;
                    readIndex++;
                    HaveWriteTo++;
                }
            }
            for (int j = 0; j < Image24bit.Height; j++)
            {
                for (int i = 0; i < Image24bit.Width; i++)
                {
                    Image24bit.SetPixel(i, j, palette[AllPixelData[index]]);
                    index++;
                }
            }
            return Image24bit;
        }

        //解析24位全真彩圖像
        private byte[] decode24(byte[] FileBytes)
        {
            int lineWidth = FileHead.BytesPerLine;
            byte[] RowRGBData = new byte[lineWidth * 3];
            int HaveWriteTo = 0;
            int WriteToRGB = 2;
            while (true)
            {
                byte ByteValue = FileBytes[readIndex];
                if (ByteValue > 0xC0)
                {
                    int Count = ByteValue - 0xC0;
                    readIndex++;
                    for (int i = 0; i < Count; i++)
                    {
                        if (HaveWriteTo + i >= lineWidth)
                        {
                            i = 0;
                            HaveWriteTo = 0;
                            WriteToRGB--;
                            Count = Count - i;
                            if (WriteToRGB == -1) break;
                        }
                        int RGBIndex = (i + HaveWriteTo) * 3 + WriteToRGB;
                        RowRGBData[RGBIndex] = FileBytes[readIndex];
                    }
                    HaveWriteTo += Count;
                    readIndex++;
                }
                else
                {
                    int RGBIndex = HaveWriteTo * 3 + WriteToRGB;
                    RowRGBData[RGBIndex] = ByteValue;
                    readIndex++;
                    HaveWriteTo++;
                }
                if (HaveWriteTo >= lineWidth)
                {
                    HaveWriteTo = 0;
                    WriteToRGB--;
                }
                if (WriteToRGB == -1) break;

            }
            return RowRGBData;
        }
    }
    /*
        读PCX尾部调色盘 
     */
    class readTailPalette
    {
        byte[] imageTailPalette = new byte[768];

        public readTailPalette() { }

        public readTailPalette(String FilePath)
        {
            byte[] imageFile = File.ReadAllBytes(FilePath);
            Array.Copy(imageFile, imageFile.Length - 768, imageTailPalette, 0, 768);
        }

        public readTailPalette(byte[] FileBytes)
        {
            Array.Copy(FileBytes, FileBytes.Length - 768, imageTailPalette, 0, 768);
        }

        public byte[] TailPaletee { get { return imageTailPalette; } }

        //获得调色盘颜色数组
        public Color[] getPalette()
        {
            int k = 3;
            Color[] palette = new Color[256];
            Color RGB;
            for (int i = 0; i < 256; i++)
            {
                RGB = Color.FromArgb(imageTailPalette[i * k], imageTailPalette[i * k + 1], imageTailPalette[i * k + 2]);
                palette.SetValue(RGB, i);
            }
            return palette;
        }
    }
}

仅为个人理解,如有不足,请指教。  https://blog.csdn.net/weixin_35811044

猜你喜欢

转载自blog.csdn.net/weixin_35811044/article/details/83997166