• GDI+中GIF图片的显示


    某位网友曾经问过我GDI+中Gif图像显示的问题,一直没时间给你写,在此致歉。我把这篇文章送给他。

    一、GIF格式介绍

    1.概述

    GIF(Graphics Interchange Format,图形交换格式)文件是由 CompuServe公司开发的图形文件格式,版权所有,任何商业目的使用均须 CompuServe公司授权。

    GIF图象是基于颜色列表的(存储的数据是该点的颜色对应于颜色列表的索引值),最多只支持8位(256色)。GIF文件内部分成许多存储块, 用来存储多幅图象或者是决定图象表现行为的控制块, 用以实现动画和交互式应用。GIF文件还通过LZW压缩算法压缩图象数据来减少图象尺寸。

    2.GIF文件存储结构

    GIF文件内部是按块划分的,包括控制块( Control Block )和数据块(Data Sub-blocks)两种。控制块是控制数据块行为的,根据不同的控制块包含一些不同的控制参数; 数据块只包含一些8-bit的字符流,由它前面的控制块来决定它的功能,每个数据块大小从0到255个字节, 数据块的第一个字节指出这个数据块大小(字节数), 计算数据块的大小时不包括这个字节,所以一个空的数据块有一个字节,那就是数据块的大小0x00。 下表是一个数据块的结构:

    BYTE 7 6 5 4 3 2 1 0 BIT
    0

    块大小

    Block Size - 块大小,不包括这个这个字节(不计算块大小自身)
    1   Data Values - 块数据,8-bit的字符串
    2  
    ...  
    254  
    255  

    一个GIF文件的结构可分为文件头(File Header)、GIF数据流(GIF Data Stream)和文件终结器(Trailer)三个部分。文件头包含GIF文件署名(Signature)和版本号(Version);GIF数据流由控制标识符、图象块(Image Block)和其他的一些扩展块组成;文件终结器只有一个值为0x3B的字符('';'')表示文件结束。下表显示了一个GIF文件的组成结构:

      GIF署名 文件头  
      版本号
      逻辑屏幕标识符 GIF数据流  
      全局颜色列表  
      ...  
      图象标识符 图象块  
      图象局部颜色列表图
      基于颜色列表的图象数据  
     
      ...  
      GIF结尾 文件结尾  

    下面就具体介绍各个部分:

    文件头部分(Header)

    GIF署名(Signature)和版本号(Version)

    GIF署名用来确认一个文件是否是GIF格式的文件,这一部分由三个字符组成:"GIF";文件版本号也是由三个字节组成,可以为"87a"或"89a".具体描述见下表:

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 ''G'' GIF文件标识
    2 ''I''
    3 ''F''
    4 ''8'' GIF文件版本号:87a - 1987年5月
            89a - 1989年7月
    5 ''7''或''9''
    6 ''a''

    GIF数据流部分(GIF Data Stream)

    逻辑屏幕标识符(Logical Screen Descriptor)

    这一部分由7个字节组成,定义了GIF图象的大小(Logical Screen Width & Height)、颜色深度(Color Bits)、背景色(Blackground Color Index)以及有无全局颜色列表(Global Color Table)和颜色列表的索引数(Index Count),具体描述见下表:

    BYTE 7 6 5 4 3 2 1 0 BIT  
    1 逻辑屏幕宽度 像素数,定义GIF图象的宽度
    2
    3 逻辑屏幕高度 像素数,定义GIF图象的高度
    4
    5 m cr s pixel 具体描述见下...
    6 背景色 背景颜色(在全局颜色列表中的索引,如果没有全局颜色列表,该值没有意义)
    7 像素宽高比 像素宽高比(Pixel Aspect Radio)

    m - 全局颜色列表标志(Global Color Table Flag),当置位时表示有全局颜色列表,pixel值有意义.

    cr - 颜色深度(Color ResoluTion),cr+1确定图象的颜色深度.

    s - 分类标志(Sort Flag),如果置位表示全局颜色列表分类排列.

    pixel - 全局颜色列表大小,pixel+1确定颜色列表的索引数(2的pixel+1次方).

    全局颜色列表(Global Color Table)

    全局颜色列表必须紧跟在逻辑屏幕标识符后面,每个颜色列表索引条目由三个字节组成,按R、G、B的顺序排列。

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 索引1的红色值  
    2 索引1的绿色值  
    3 索引1的蓝色值  
    4 索引2的红色值  
    5 索引2的绿色值  
    6 索引2的蓝色值  
    7 ...  

    图象标识符(Image Descriptor)

    ~~~~~~~~~~~~~~~~~~~~~~~~~

    一个GIF文件内可以包含多幅图象,一幅图象结束之后紧接着下是一幅图象的标识符,图象标识符以0x2C('','')字符开始, 定义紧接着它的图象的性质,包括图象相对于逻辑屏幕边界的偏移量、图象大小以及有无局部颜色列表和颜色列表大小, 由10个字节组成:

    BYTE 7 6 5 4 3 2 1 0 BIT  
    1 0 0 1 0 1 1 0 0 图象标识符开始,固定值为'',''
    2 X方向偏移量 必须限定在逻辑屏幕尺寸范围内
    3
    4 Y方向偏移量
    5
    6 图象宽度
    7
    8 图象高度
    9
    10 m i s r pixel m - 局部颜色列表标志(Local Color Table Flag)
                  置位时标识紧接在图象标识符之后有一个局部颜色列表,供紧跟在它之后的一幅图象使用;值否时使用全局颜色列表, 忽略pixel值。
    i - 交织标志(Interlace Flag),置位时图象数据使用交织方式排列 (详细描述...),否则使用顺序排列。
    s - 分类标志(Sort Flag),如果置位表示紧跟着的局部颜色列表分类排列.
    r - 保留,必须初始化为0.
    pixel - 局部颜色列表大小(Size of Local Color Table),pixel+1就为颜色列表的位数

    局部颜色列表(Local Color Table)

    如果上面的局部颜色列表标志置位的话,则需要在这里(紧跟在图象标识符之后)定义一个局部颜色列表以供紧接着它的图象使用,注 意使用前应线保存原来的颜色列表,使用结束之后回复原来保存的全局颜色列表。如果一个GIF文件即没有提供全局颜色列表,也没有提供局部颜色列表, 可以自己创建一个颜色列表,或使用系统的颜色列表。局部颜色列表的排列方式和全局颜色列表一样:RGBRGB......

    基于颜色列表的图象数据(Table-Based Image Data)

    由两部分组成:LZW编码长度(LZW Minimum Code Size)和图象数据(Image Data)。

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 LZW编码长度 LZW编码初始码表大小的位数,详细描述见LZW编码...

     


    ...
    图象数据,由一个或几个数据块(Data Sub-blocks)组成

    数据块

    ...

    GIF图象数据使用了LZW压缩算法(详细介绍请看后面的『LZW算法和GIF数据压缩』),大大减小了图象数据的大小。图象数据在压缩前有两种排列格式:

    连续的和交织的(由图象标识符的交织标志控制)。连续方式按从左到右、从上到下的顺序排列图象的光栅数据;交织图象按下面的方法处理光栅数据:

    创建四个通道(pass)保存数据,每个通道提取不同行的数据:

    第一通道(Pass 1)提取从第0行开始每隔8行的数据;

    第二通道(Pass 2)提取从第4行开始每隔8行的数据;

    第三通道(Pass 3)提取从第2行开始每隔4行的数据;

    第四通道(Pass 4)提取从第1行开始每隔2行的数据;

    下面的例子演示了提取交织图象数据的顺序:

    通道1 通道2 通道3 通道4  
    0  -------------------------------------------------------- 1        
    1 --------------------------------------------------------       4  
    2  --------------------------------------------------------     3    
    3  --------------------------------------------------------       4  
    4  --------------------------------------------------------   2      
    5  --------------------------------------------------------       4  
    6  --------------------------------------------------------     3    
    7  --------------------------------------------------------       4  
    8  -------------------------------------------------------- 1        
    9  --------------------------------------------------------       4  
    10 --------------------------------------------------------     3    
    11 --------------------------------------------------------       4  
    12 --------------------------------------------------------   2      
    13 --------------------------------------------------------       4  
    14 --------------------------------------------------------     3    
    15 --------------------------------------------------------       4  
    16 -------------------------------------------------------- 1        
    17 --------------------------------------------------------       4  
    18 --------------------------------------------------------     3    
    19 --------------------------------------------------------       4  
    20 --------------------------------------------------------   2      

    图形控制扩展(Graphic Control Extension)

    这一部分是可选的(需要89a版本),可以放在一个图象块(图象标识符)或文本扩展块的前面, 用来控制跟在它后面的第一个图象(或文本)的渲染(Render)形式,组成结构如下:

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 扩展块标识 Extension Introducer - 标识这是一个扩展块,固定值0x21
    2 图形控制扩展标签 Graphic Control Label - 标识这是一个图形控制扩展块,固定值0xF9
    3 块大小 Block Size - 不包括块终结器,固定值4
    4 保留 处置方法

    i

    t

    i - 用户输入标志;t - 透明色标志。详细描述见下...
    5 延迟时间 Delay Time - 单位1/100秒,如果值不为1,表示暂停规定的时间后再继续往下处理数据流
    6
    7 透明色索引 Transparent Color Index - 透明色索引值
    8 块终结器 Block Terminator - 标识块终结,固定值0

    处置方法(Disposal Method):指出处置图形的方法,当值为:

                            0 - 不使用处置方法

                            1 - 不处置图形,把图形从当前位置移去

                            2 - 回复到背景色

                            3 - 回复到先前状态

                          4-7 - 自定义

    用户输入标志(Use Input Flag):指出是否期待用户有输入之后才继续进行下去,置位表示期待,值否表示不期待。用户输入可以是按回车键、鼠标点击等, 可以和延迟时间一起使用,在设置的延迟时间内用户有输入则马上继续进行,或者没有输入直到延迟时间到达而继续

    透明颜色标志(Transparent Color Flag):置位表示使用透明颜色

    注释扩展(Comment Extension)

    这一部分是可选的(需要89a版本),可以用来记录图形、版权、描述等任何的非图形和控制的纯文本数据(7-bit ASCII字符),注释扩展并不影响对图象数据流的处理,解码器完全可以忽略它。 存放位置可以是数据流的任何地方,最好不要妨碍控制和数据块,推荐放在数据流的开始或结尾。具体组成:

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 扩展块标识 Extension Introducer - 标识这是一个扩展块,固定值0x21
    2 注释块标签 Comment Label - 标识这是一个注释块,固定值0xFE
     
    ...
    Comment Data - 一个或多个数据块(Data Sub-Blocks)组成

    注释块

    ...
      块终结器 Block Terminator - 标识注释块结束,固定值0

    图形文本扩展(Plain Text Extension)

    这一部分是可选的(需要89a版本),用来绘制一个简单的文本图象,这一部分由用来绘制的纯文本数据(7-bit ASCII字符)和控制绘制的参数等组成。绘制文本借助于一个文本框(Text Grid)来定义边界,在文本框中划分多个单元格,每个字符占用一个单元,绘制时按从左到右、从上到下的顺序依次进行, 直到最后一个字符或者占满整个文本框(之后的字符将被忽略,因此定义文本框的大小时应该注意到是否可以容纳整个文本), 绘制文本的颜色索引使用全局颜色列表,没有则可以使用一个已经保存的前一个颜色列表。另外,图形文本扩展块也属于图形块(Graphic Rendering Block),可以在它前面定义图形控制扩展对它的表现形式进一步修改。图形文本扩展的组成:

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 扩展块标识 Extension Introducer - 标识这是一个扩展块,固定值0x21
    2 图形控制扩展标签 Plain Text Label - 标识这是一个图形文本扩展块,固定值0x01
    3 块大小 Block Size - 块大小,固定值12
    4 文本框左边界位置 Text Glid Left Posotion - 像素值,文本框离逻辑屏幕的左边界距离
    5
    6 文本框上边界位置 Text Glid Top Posotion - 像素值,文本框离逻辑屏幕的上边界距离
    7
    8 文本框高度 Text Glid Width -像素值
    9
    10 文本框高度 Text Glid Height - 像素值
    11
    12 字符单元格宽度 Character Cell Width - 像素值,单个单元格宽度
    13 字符单元格高度 Character Cell Height- 像素值,单个单元格高度
    14 文本前景色索引 Text Foreground Color Index - 前景色在全局颜色列表中的索引
    15 文本背景色索引 Text Blackground Color Index - 背景色在全局颜色列表中的索引
    N
    ...
    Plain Text Data - 一个或多个数据块(Data Sub-Blocks)组成,保存要在显示的字符串。

    文本数据块

    ...
    N+1 块终结 Block Terminator - 标识注释块结束,固定值0

    推荐:1.由于文本的字体(Font)和尺寸(Size)没有定义,解码器应该根据情况选择最合适的;

    2.如果一个字符的值小于0x20或大于0xF7,则这个字符被推荐显示为一个空格(0x20);

    3.为了兼容性,最好定义字符单元格的大小为8x8或8x16(宽度x高度)。

    应用程序扩展(Application Extension)

    这是提供给应用程序自己使用的(需要89a版本),应用程序可以在这里定义自己的标识、信息等,组成:

    BYTE 7 6 5 4 3 2 1 0 BIT
    1 扩展块标识 Extension Introducer - 标识这是一个扩展块,固定值0x21
    2 图形控制扩展标签 Application Extension Label - 标识这是一个应用程序扩展块,固定值0xFF
    3 块大小 Block Size - 块大小,固定值11
    4 应用程序标识符 Application Identifier - 用来鉴别应用程序自身的标识(8个连续ASCII字符)
    5
    6
    7
    8
    9
    10
    11
    12 应用程序鉴别码 Application Authentication Code - 应用程序定义的特殊标识码(3个连续ASCII字符)
    13
    14
    N
    ...
    应用程序自定义数据块 - 一个或多个数据块(Data Sub-Blocks)组成,保存应用程序自己定义的数据

    应用程序数据

    ...
    N+1 块终结器 lock Terminator - 标识注释块结束,固定值0

    文件结尾部分

    文件终结器(Trailer)

    这一部分只有一个值为0的字节,标识一个GIF文件结束.

    BYTE 7 6 5 4 3 2 1 0  
    1

    文件终结

    GIF Trailer - 标识GIF文件结束,固定值0x3B

    二、在GDI+中绘制GIF

    GDI+中绘制一个图片的代码如下:

    1.void CMyWnd::OnPaint()
    2.{
    3.CPaintDC dc(this);
    4.Graphics graphics(&dc); // Create a GDI+ graphics object
    5. 
    6.Image image(L"Test.Gif"); // Construct an image
    7.graphics.DrawImage(&image, 0, 0, image.GetWidth(), image.GetHeight());
    8.}

    Gif分为两种,一种是静态的,对于这种格式的Gif,在GDI+中无需采用任何方法就能够直接显示(上面的代码就属于这种情况)。另一种是动态的, 这种文件能够显示简单的动画。动态的实际上由多幅静态的组成,在显示Gif时,每幅图片按照一定的时间间隔依次进行显示,从而实现了动画效果。

    我把GIF封装成了一个类ImageEx,这个类继承了GDI+中的Image类。我们首先要做的工作是判断GIF是动态的还是静态的。

    01.bool ImageEx::TestForAnimatedGIF()
    02.{
    03.UINT count = 0;
    04.count = GetFrameDimensionsCount();
    05.GUID* pDimensionIDs = new GUID[count];
    06. 
    07.// 得到子帧的对象列表
    08.GetFrameDimensionsList(pDimensionIDs, count);
    09. 
    10.//获取总帧数
    11.m_nFrameCount = GetFrameCount(&pDimensionIDs[0]);
    12. 
    13.// 假设图像具有属性条目 PropertyItemEquipMake.
    14.// 获取此条目的大小.
    15.int nSize = GetPropertyItemSize(PropertyTagFrameDelay);
    16. 
    17.// 为属性条目分配空间.
    18.m_pPropertyItem = (PropertyItem*) malloc(nSize);
    19.GetPropertyItem(PropertyTagFrameDelay, nSize, m_pPropertyItem);
    20.delete pDimensionIDs;
    21.return m_nFrameCount > 1;
    22. 
    23.}

    m_pPropertyItem->value 是一个长整形数组, 每个长整形代表每帧的延时。由于获取的属性不同,GetPropertyItem会获得不同大小的对象, 因此要由用户来获得的对象大小,开辟与删除 GetPropertyItem相关的内存。对象的大小是通过GetPropertyItemSize 获取的,其参数是你所感兴趣的属性条目。 一旦获取了帧的数量与延时,我们就可生成一个线程来调用 DrawFrameGIF()来显示。

    01.bool ImageEx::DrawFrameGIF()
    02.{
    03.::WaitForSingleObject(m_hPause, INFINITE);
    04.GUID pageGuid = FrameDimensionTime;
    05.long hmWidth = GetWidth();
    06.long hmHeight = GetHeight();
    07.HDC hDC = GetDC(m_hWnd);
    08.if (hDC)
    09.{
    10.Graphics graphics(hDC);
    11.graphics.DrawImage(this, m_rc.left, m_rc.top, hmWidth, hmHeight);
    12.ReleaseDC(m_hWnd, hDC);
    13.}
    14.SelectActiveFrame(&pageGuid, m_nFramePosition++);
    15.if (m_nFramePosition == m_nFrameCount)
    16.m_nFramePosition = 0;  
    17. 
    18.long lPause = ((long*) m_pPropertyItem->value)[m_nFramePosition] * 10;
    19.DWORD dwErr = WaitForSingleObject(m_hExitEvent, lPause);
    20.return dwErr == WAIT_OBJECT_0;
    21.}
    1. 

    三、效果图

    图一 效果

    四、结束语

    本人无偿提供绘图软件与数据库软件技术咨询。有偿提供算法,控件,组件,绘图软件与数据库软件的开发。

    邮件地址:realman1981@sohu.com。

    电话:13679278016。

  • 相关阅读:
    poj 3280 Cheapest Palindrome(区间DP)
    POJ 2392 Space Elevator(多重背包)
    HDU 1285 定比赛名次(拓扑排序)
    HDU 2680 Choose the best route(最短路)
    hdu 2899 Strange fuction (三分)
    HDU 4540 威威猫系列故事――打地鼠(DP)
    HDU 3485 Count 101(递推)
    POJ 1315 Don't Get Rooked(dfs)
    脱离eclipse,手动写一个servlet
    解析xml,几种方式
  • 原文地址:https://www.cnblogs.com/jinsedemaitian/p/5589091.html
Copyright © 2020-2023  润新知