版权声明:此为个人学习与研究成果,若需转载请提前告知。 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头文件,最重要的是解读这几个部分:
- 版本号,若为5,通常在文件最末尾都有一个256色的调色盘,即文件的最后的768 bytes为调色盘数据,按顺序以R/G/B,每三个bytes构成一个调色盘的颜色,通常倒数的第769个bytes会是一个鉴定位,值一般为12,表示后面有256色调色盘。另外pcx档是支持24位全真彩的。
- 像素位 (第3个字节) 和 彩色平面数 (第65个字节) ,决定了图像的类别。如色彩平面数大于1 ,那就没有使用调色盘。例如:像素位:8 ,平面数:1 ,为256色图像,使用了末尾调色盘。像素位:8 ,平面数:3 ,为24为全彩图像,没有使用调试盘。
- 图像大小(第4个字节),可以得到整个图像的像素个数还有宽、高。要注意的是第一个像素是(0,0),所以算真实个数(宽、高)时:(Xmax - Xmin)+1,(Ymax - Ymin)+1。
起始字节 |
字节数 |
内容 |
解释 |
0 |
1 |
Zsoft标志 |
为10(0x0a),即为Zsoft PCX文件 |
1 |
1
扫描二维码关注公众号,回复:
4128877 查看本文章
|
版本号 |
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图案为例。
- 在压缩的PCX位图数据中,分为两种数据:一种是记录数据(某一pixel值的个数),一种是真实数据(pixel值)。
- PCX的位图数据里,每个数据8个bit: a)如果最高两位为11,此数据为记录数据,记录的是下一个数据作为pixel值将在原图中连续出现的次数(与192的差值)。ex:一个数据11000011(195) 比11000000(192)大3 --> 即下一个数据的值是原图中连续3个的pixel值。b)如果最高位不是11,则此数据为真实数据,就是原图对应的pixel值。ex:01000000(64),原图对应pixel值就为64。
- 如果原图中的pixel值本身就大于192,即高位也为11,那么在读压缩数据时会无法判断此数据是记录数据还是真实数据。解决办法是,原图中如果大于192的pixel值就算只有1个,在压缩数据中也要用一个记录数据去记录它。这样不管如何,读到高位为11的数据,皆为记录数据。
- 如下图:
假设在压缩的PCX位图数据中,一段数据为:
那么这一段数据,解压后的原图pixel数据为:
- C#实作:读取两类pcx图档。
- one plane & 8 bits/pixel
- 使用调色盘,256色图。
- pixel值表示调色盘索引,指向调色盘。
- three planes & 8 bits/pixel
- 未使用调色盘,全彩图。
- 位图数据有序的按原图每一行记录,先记录每行所有R值、再记录G值、最后记录B值,换行时可能有0作为换行指标。
- 在C#的bitmap类中,RGB数组中存储顺序为[2][1][0]。
- one plane & 8 bits/pixel
代码:完整读取两类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