告诉您:GIF这种压缩标准是如何进行计算的,以及动手压缩图片
我们知道,位图文件和图标文件都是以像素为单位进行图像信息记录的,这样的记录方式生成的文件十分庞大,称为未压缩文件,在单机上使用是可以的,如果要在网上传播是不受欢迎的。如何对图像进一步压缩呢?GIF就是常用的压缩技术,也是网上流行的图像文件格式。
GIF压缩标准:
GIF作为一种压缩标准,受到了很多软件公司的普遍支持,当然也包含微软公司。GIF有GIF87a 和GIF89a两种版本,而且是早期动画使用的格式。
GIF是如何进行压缩的?它首先把图像转化成8位的256色图像(或128色、64色、32色、16色、8 色、4色、2色都可以),以256色为例,表示一个像素就需要8位的二进制信息,即1个字节Byte。接着GIF根据图像中像素的排列规律,建立一个类似于颜色排列表,用一个个9位的整数值(Integer)来表示像素的排列规律,达到压缩的目的(它的名字叫LZW 压缩算法)。听起来是不是不好理解,后面我会举例来说明的。最后在保存到文件时,再把9位的整数(表示范围为0~511)转换成8位的Byte类型的值,保存到GIF文件中。
颜色排列表:
假设有一串图像的像素流(存储在byte数值类型的buf () 数组中)进行GIF压缩,这串数据流的前20个,即buf (0) ~ buf (19) 的值分别为:
8 8 8 8 99 99 8 8 8 8
8 8 8 99 99 8 99 8 99 8
数据流的第一个数值被记录在文件的信息部分,真正的压缩计算都是从第二个开始的:
运算的程序代码:
bint(0) = 256
sP = Right$("00" & buf(0), 3)
iCode = buf(0)
For i = 1 To 19
…
…
sB = Right$("00" & buf(i), 3)
sP = sP & sB
On Error Resume Next
iCode = colTable(sP) ’ 集合中是否已经保存了需要的成员
If Err <> 0 Then ’没有找到时
n = colTable.Count
colTable.Add n + 258, sP ’ 添加成员及索引
j = j + 1
bint(j) = iCode ’保存转化后的值到要保存的整数集合中
sP = sB
iCode = buf(i)
Err.Clear
End If
Next
运算过程中各变量的变化为:
buf()序号 colTable bint()值
成员 索引
256
1 258 008008 8
3 259 008008008 258
4 260 008099 8
5 261 099099 99
6 262 099008 99
9 263 008008008008 259
13 264 008008008008099 263
15 265 099099008 261
17 266 008099008 260
260
由上面计算的结果看,可以说明几个问题:
1. 文件被压缩了,20个byte 被压缩成10个9位的Integer ,这是因为颜色的规律被保存在颜色排列表的集合中,用一个数值就能同时表示几个像素的信息。
2. 压缩后的文件大小除了和图像的尺寸(像素流的长短)有关系以外,图像本身的复杂程度也影响着压缩文件的大小,图像越简单(相同颜色的块越大),压缩文件就越小。
3. 为了确保转换后的整数值不超过9 位,因此颜色排列表中的成员只能是256~511 ,又因为256 ,257 被保留为特殊标志,实际上可用的只有258~511 共254个,如果超过,就要清除旧表,重新建立(256是重新初始化颜色排列表的标志,257是像素流结束的标志)
4. 有一点必须说明,在转换256色像素流时,与位图不同的是,它是从图像的左上角开始,遂行向下扫描的。如果是用API函数来获得像素流时,必须把流重新排列。
GIF压缩的特点:
GIF压缩前必须把图像转换成256色以下的图像,因此图像的质量大打折扣,但是,我们从上面的压缩结果来看,原图像像素流中的每一个值都能被准确地描叙、保存下来。也就是说,在GIF解压缩时,能准确无误地还原出图像上的每个像素(如果原图为256色以下图像时)。因此GIF压缩也被称为无损压缩,这种压缩能准确地体现图形的清晰的边缘,如果图形是以文字扫描为主,GIF压缩绝对是最佳的首选。
GIF文件的组成:
GIF文件的组成包含以下几个部分:
版本 文件头 系统调色盘 [辅助信息] 图片头信息 图片压缩信息 结束标志
(6bytes) (7bytes) (8bytes) (11bytes) ((255+255+ +n )bytes) (2bytes)
版本:GIF89a 或 GIF87a
辅助信息:只有在GIF透明显示或GIF动画文件中才有。也是为了实现这些特性而设置的。
系统调色盘:位图和图标文件的调色盘中每个颜色是4个字节,而GIF文件中每个颜色是3个字节。
图片压缩信息:因为图像压缩后信息量的大小与图像本身的复杂程度有关,它不像位图一样,只要知道图像的大小,就能计算出文件大小来,而是要等到像素流全部压缩完才能知道文件的大小。因此,GIF文件中的图片压缩信息部分,是以254字节大小为单元,向后申请的(每个单元再前面加上一个字节表示本单元的长度,实际每单元共255个字节),最后一单元是以实际大小来申请的。
结束标志:以0 59 作为文件结束标志。
编程举例:
为了实现GIF的压缩,用VB6 建一个工程,窗体名为“BnpToGif”, ScaleMode =3-Pixel;一个文本框用来输入要打开的文件名或要保存的文件名;两个按钮;三个标签;两个图片框,Picture2 用来显示文件压缩的完成进度,Width :400 ,Height:13。Picture1 用来显示打开的位图,Visible=false ,AutoRedraw=true ,AutoSize=true ,ScaleMode =3-Pixel, BorderStyle=0-None。
“Save”按钮初始时Enabled=false ,待按“Open”打开Text1 输入的位图文件完成后,才变为可用状态,单击可以把原位图转换成256色GIF文件(自动更换后缀名或另存为Text1中重新输入的文件名)
程序代码
Option Explicit
Private Type RGBTRIPLE ’调色盘
rgbRed As Byte
rgbGreen As Byte
rgbBlue As Byte
End Type
Private Type GifScreenDescriptor ’文件头
logical_screen_width As Integer ’图像宽
logical_screen_height As Integer ’图像高
Flags As Byte ’文件的质量(调色盘中颜色多少)的标志
background_color_index As Byte ’保存像素流第一个像素的值
pixel_aspect_ratio As Byte ’为0
End Type
’文件头中Flags 在256色时为231;128色时为198;64色时为165;32色时为132;16色时为227;8色时为194;4色时为161
Private Type GifImageDescriptor ’图片信息头
ImageSeparator As Byte ’为44
Left As Integer ’左
Top As Integer ’右
Width As Integer ’宽
Height As Integer ’高
Format As Byte ’为0
data As Byte ’为每个像素占的位数
End Type
Private Type GifImageEnd ’文件结束标志
dat1 As Byte ’为0
dat2 As Byte ’为59
End Type
Private Const GIF89a = "GIF89a"
Dim colTable As New Collection '用于LZW压缩时的颜色排列表
Dim bitpos, bitval As Byte '用于位运算时的位定位及转化后的Byte值,并把该值写入gfbyt(p255)中
Dim p255 As Long '写入文件的总字节数(定位)
Dim gfbyt() As Byte '写入文件的字节流
Dim buf() As Byte '图像像素流(256色或以下)
Dim bpos(12) As Long '用于位比较时的数1,2,4,8。。。。
Dim fname As String
'把gfbyt()分段(254个为一段)并写入文件
Private Sub putByte()
Dim i, k As Integer
Dim j As Long
k = Int(p255 / 254)
Dim sz() As Byte
ReDim sz(k)
sz(k) = p255 Mod 254
For i = k - 1 To 0 Step -1: sz(i) = 254: Next
k = 0
For j = 0 To p255
If j Mod 254 = 0 Then Put #1, , sz(k): k = k + 1
Put #1, , gfbyt(j)
Next
End Sub
'把压缩后的表值写入gfbyt()
'在256色时,表值为变长(9-12位)的Integer,256为清除旧表,257为图像结束,表项为258—4095共3837个
'在4色时,表值为变长(3-12位)的Integer,4为清除旧表,5为图像结束,表项为6—4095共4089个
Private Sub AddByte(ByVal bVal As Long, ByVal vLen As Byte)
Dim i As Byte
For i = 0 To vLen - 1
If (bVal And bpos(i)) Then bitval = bitval + bpos(bitpos)
bitpos = bitpos + 1
If bitpos = 8 Then
bitpos = 0
gfbyt(p255) = bitval
bitval = 0
p255 = p255 + 1
ReDim Preserve gfbyt(p255) '再申请一个Byte并保留原来数据
gfbyt(p255) = 0
End If
Next
End Sub
Private Sub Command1_Click()
fname = Text1.Text
If Right(fname, 3) <> "bmp" Then Label3.Caption = "文件不是 .bmp 文件": Exit Sub
If Dir(fname) = "" Then Label3.Caption = "文件不存在": Exit Sub
Picture1.Picture = LoadPicture(fname)
Command2.Enabled = True
End Sub
Private Sub Command2_Click()
'检测保存文件名的正确性
fname = Text1.Text
Dim i, j As Integer
Dim pw, ph As Integer
i = InStr(1, fname, "/")
Do While i > 0: j = i: i = InStr(1, fname, "/"): Loop
If Dir(Mid(fname, 1, j), vbDirectory) = "" Then Label3.Caption = "文件路径不存在": Exit Sub
If Right(fname, 4) <> ".gif" Then fname = Mid(fname, 1, Len(fname) - 4) & ".gif"
DoEvents
jindu 10
'转换图像为256色,并存入buf ()像素流中
Dim r, g, b, rA, gA, bA, lIndex As Integer
Dim col As Long
Dim k, kk As Long
ph = Picture1.Height - 1
pw = Picture1.Width - 1
kk = (ph + 1) * (pw + 1) - 1
ReDim buf(kk) As Byte
'转换时使用的计算方法要和建立调色盘的算法相一致
For i = 0 To ph
For j = 0 To pw
col = Picture1.Point(j, i)
If col = 16777215 Then
buf(k) = 215
Else
b = Int(col / 65536)
col = col - b * 65536
g = Int(col / 256)
r = col Mod 256
rA = CInt(r / 51)
gA = CInt(g / 51)
bA = CInt(b / 51)
buf(k) = bA * 36 + gA * 6 + rA
End If
k = k + 1
Next
Next
DoEvents
jindu 50
'写入GIF文件
Dim scr As GifScreenDescriptor
Dim im As GifImageDescriptor
Dim gifPalette(0 To 255) As RGBTRIPLE
Dim gend As GifImageEnd
Dim sPrefix As String
Dim sByte As String
Dim intCode As Integer
Dim nCount As Byte
’建立系统调色盘中的颜色,创建216个,前面转换256色像素的计算方法要和这里的创建方法相一致!
For b = 0 To 255 Step 51
For g = 0 To 255 Step 51
For r = 0 To 255 Step 51
gifPalette(lIndex).rgbBlue = b
gifPalette(lIndex).rgbGreen = g
gifPalette(lIndex).rgbRed = r
lIndex = lIndex + 1
Next
Next
Next
scr.background_color_index = 215
scr.Flags = 231
scr.pixel_aspect_ratio = 0
scr.logical_screen_width = Picture1.Width
scr.logical_screen_height = Picture1.Height
im.ImageSeparator = 44
im.data = 8
im.Format = 0
im.Height = Picture1.Height
im.Width = Picture1.Width
'写入GIF文件头
Open fname For Binary As #1
Put #1, , GIF89a
Put #1, , scr
Put #1, , gifPalette
Put #1, , im
'进行GIF压缩,从 buf() 到 gfbyt()
Dim l As Integer
ReDim gfbyt(0)
gfbyt(0) = 0
l = 9
AddByte 256, l
sPrefix = Right$("00" & buf(0), 3)
intCode = buf(0)
r = 0
For k = 1 To bufsize - 1
sByte = Right$("00" & buf(k), 3)
sPrefix = sPrefix & sByte
On Error Resume Next
intCode = colTable(sPrefix)
If Err <> 0 Then
nCount = colTable.Count
If nCount = 3837 Then
AddByte intCode, l
Set colTable = Nothing
Set colTable = New Collection
AddByte 256, l
l = 9
sPrefix = sByte
intCode = buf(k)
GoTo 10
End If
colTable.Add nCount + 258, sPrefix
If getlen(nCount + 257) > l Then l = l + 1 '根据需要增加字节位数
AddByte intCode, l
sPrefix = sByte
intCode = buf(k)
Err.Clear
End If
10:
Next
AddByte intCode, l '把最后一个压缩值写入
nCount = colTable.Count
If nCount > 3837 Then AddByte 256, 9
AddByte 257, 9 '写入结束标志
putByte '写入文件
gend.dat1 = 0
gend.dat2 = 59
Put #1, , gend
Close #1
Erase buf
Erase gfbyt
Label3.Caption = "成功写入" & fname & "文件"
jindu 100
End Sub
Private Sub jindu(ByVal vol As Integer) ’用于显示进度
Picture2.Cls
Label2.Caption = vol & " %"
Picture2.Line (0, 0)-Step(vol * 4, 13), , BF
DoEvents
End Sub
Private Function getlen(ByVal bVal As Long) As Byte
Select Case bVal
Case 0, 1
getlen = 1
Case Is < 4
getlen = 2
Case Is < 8
getlen = 3
Case Is < 16
getlen = 4
Case Is < 32
getlen = 5
Case Is < 64
getlen = 6
Case Is < 128
getlen = 7
Case Is < 256
getlen = 8
Case Is < 512
getlen = 9
Case Is < 1024
getlen = 10
Case Is < 2048
getlen = 11
Case Else
getlen = 12
End Select
End Function
Private Sub Form_Load()
Text1.Text = App.Path & "\yeye.bmp"
Dim i As Integer
For i = 0 To 12: bpos(i) = 2 ^ i: Next '初始化用于位运算的数
End Sub
编程举例: 电子图书格式(4色GIF):
系统调色盘中颜色的多少,直接影响着转化后图片的质量,同时也影响着压缩文件的大小,如果图片是以文字的扫描为主,我就可以用2色(黑和白)来进行压缩,就能获得最大的压缩率且不影响图片的阅读。文件头中Flags=161 (4色),调色盘中4个颜色仅设置1个(255,255,255)即可,其余3个不赋值为(0,0,0),在转换图像像素流时,转化为2色,这样就能获得最高的压缩率。现在有许多电子图书都使用这种格式(4色GIF)的压缩。
我曾用一幅B4大小(1150*800像素),约1000个字的图片,进行4色GIF压缩,我们来看下面一组数字:24位位图约2630 K;256色GIF约45 K;4色GIF约24 K。由此可见,4色GIF压缩可以获得最大的压缩比,约100 :1,而且图像质量非常好,用看图工具放大数倍后,仍然清晰。这也是GIF成为独步电子图书王国的原因。下面我就再举一个例子来展示4色GIF压缩。
在前面的程序中添加一个Command3,编写代码:
Private Sub Command3_Click()
'检测保存文件名的正确性
fname = Text1.Text
Dim i, j As Integer
Dim pw, ph As Integer
i = InStr(1, fname, "/")
Do While i > 0: j = i: i = InStr(1, fname, "/"): Loop
If Dir(Mid(fname, 1, j), vbDirectory) = "" Then Label3.Caption = "文件路径不存在": Exit Sub
If Right(fname, 4) <> ".gif" Then fname = Mid(fname, 1, Len(fname) - 4) & ".gif"
DoEvents
jindu 10
'转换图像为256色,并存入buf ()像素流中
Dim col As Long
Dim k, kk As Long
ph = Picture1.Height - 1
pw = Picture1.Width - 1
kk = (ph + 1) * (pw + 1) - 1
ReDim buf(kk) As Byte
'转换时使用的计算方法要和建立调色盘的算法相一致
For i = 0 To ph
For j = 0 To pw
col = Picture1.Point(j, i)
If col = 16777215 Then
buf(k) = 1 '这里只取两色,黑和白
Else
buf(k) = 0
End If
k = k + 1
Next
Next
DoEvents
jindu 50
'写入GIF文件
Dim scr As GifScreenDescriptor
Dim im As GifImageDescriptor
Dim gifPalette(3) As RGBTRIPLE
Dim gend As GifImageEnd
Dim sPrefix As String
Dim sByte As String
Dim intCode As Long
Dim nCount As Long
gifPalette(0).rgbBlue = 0
gifPalette(0).rgbGreen = 0
gifPalette(0).rgbRed = 0
gifPalette(1).rgbBlue = 255
gifPalette(1).rgbGreen = 255
gifPalette(1).rgbRed = 255
scr.background_color_index = 0
scr.Flags = 161
scr.pixel_aspect_ratio = 0
scr.logical_screen_width = pw
scr.logical_screen_height = ph
im.ImageSeparator = 44
im.data = 2
im.Format = 0
im.Height = ph
im.Width = pw
'写入GIF文件头
Open fname For Binary As #1
Put #1, , GIF89a
Put #1, , scr
Put #1, , gifPalette
Put #1, , im
Dim l As Integer
'进行GIF压缩,从 buf() 到 gfbyt()
ReDim gfbyt(0)
gfbyt(0) = 0
l = 3
AddByte 4, l
sPrefix = CStr(buf(0))
intCode = buf(0)
For k = 1 To bufsize - 1
sByte = buf(k)
sPrefix = sPrefix & sByte
On Error Resume Next
intCode = colTable(sPrefix)
If Err <> 0 Then
nCount = colTable.Count
If nCount = 4089 Then '表值满9位,清除旧表
AddByte intCode, l
'注意!重新颜色排列表时添加的标志“4”要写12位,而不是3位
'即 000000000100
AddByte 4, l
Set colTable = Nothing
Set colTable = New Collection
l = 3
nCount = 0
sPrefix = buf(k)
intCode = buf(k)
GoTo 10
End If
colTable.Add nCount + 6, sPrefix
If getlen(nCount + 5) > l Then l = l + 1 '根据需要增加字节位数
AddByte intCode, l
sPrefix = sByte
intCode = buf(k)
Err.Clear
End If
10:
Next
AddByte intCode, l '把最后一个压缩值写入
'写入文件
AddByte 5, l
putByte
gend.dat1 = 0
gend.dat2 = 59
Put #1, , gend
Close #1
Erase buf
Erase gfbyt
Label3.Caption = "成功写入" & fname & "文件"
jindu 100
End Sub