用最快速度编写和发布的图标导出工具v1.0版

=============================================================================================

      声明【1】:这篇文章中的代码在写ICO文件时的方法并不够完善和准确。因此保存ICO文件时会有些小问题。目前看来,有两种方法,可能需要把微软的C++的范例源码借鉴并引入到C#项目中,另一种是把该范例改写为一个DLL,然后用PInvoke方式调用。总之,不管采用哪种解决方法,将会改变本文中的技术路线,因为代码修改量比较大,细节也非常繁琐,因此这不是一个短时间内可以完成的任务,暂且放置这样一个声明于此,以提醒借鉴者能注意(这个声明的原因是,一是对自己,本着求是和追求完美的态度,我不能容忍自己的技术日志有缺陷乃至关键性的逻辑错误;二是对他人,本着对潜在读者负责的态度,尽可能不对他人造成误解和负面影响)。我将在时间充裕的时候尽可能快的解决这个问题(有问题的代码暂时放置于此)。

                                                                                       ------ hoodlum1980  于 2008年9月26日20:19:02。

============================================================================================

=============================================================================================

  声明【2】:时隔两年后,我重新研究了一下ICO文件格式,并修改了代码。因此声明 1 作废。具体细节已补充到原文结尾处。

                                                                                             ------ hoodlum1980  于 2010年8月23日

============================================================================================

 

      最近发现我的自动关机程序的图标有点丑陋和粗糙,(是我自己制作的一个qq的老鼠头像),放在桌面上和其他xp图标一比不免相形见绌。于是突发奇想想把自动关机程序的图标换成系统的关机图标,于是我就想导出这个图标。这个图标的位置位于system32目录下的shell32.dll中,vs.net本身就可以直接打开一个模块的资源,但是我发现由于这个dll的图标太多,并且vs.net并没有提供一个方便的预览功能,所以一个个打开查看还是太慢了。于是想起这种图标导出工具很多,但是下载了几个下来一看却让我非常不爽和郁闷。因为这么简单的功能,我下了4~5个竟然没有一个免费版本,都是试用版并且注册要收费的。虽然它们安装以后能用,但是都提示你只能用10次以及经常弹出注册之类的提示,让我感到很生气。所以我干脆自己实现一个简单的。于是决定用开发一个自己用,并且把源码公开。其实很久前就有人给我看过这类工具,但是由于我觉得vs.net就能打开和导出了,再者也有人做了,所以我一直没有关注过,只是看了下相关API函数。

首先是一个应用程序截图:

把其他格式图转存为ICO格式:


我们要了解图标导出的关键API函数是:

HICON ExtractIconEx(
LPCTSTR lpszFile, 
int nIconIndex, 
HICON FAR* phiconLarge, 
HICON FAR* phiconSmall, 
UINT nIcons
); 

这个函数的参数和用法我就不多说了,在sdk文档中写的很详细。总结一下,这个函数可以获取一组图标(填充句柄到一个句柄数组),可以获取模块资源中的图标个数。好了,在c#中我们还是要用到我们熟悉的P/Invoke将它在.Net中声明:

        /// <summary>
        /// 从某个索引开始的所有图标导出到一个数组,图标使用后必须调用destroyIcon!其他用法如下:
        /// 获取图标数目:nIconIndex=-1,两个数组指针为NULL
        /// 获取指定图标:nIconIndex为指定图标资源ID的负数,例如nIconIndex为-3时获取ID为3的图标!
        /// </summary>
        /// <param name="lpszFile">[in] Pointer to a null-terminated string specifying the name of an executable file, DLL, or icon file from which icons will be extracted. </param>
        /// <param name="nIconIndex">[in] Specifies the zero-based index of the first icon to extract. </param>
        /// <param name="phiconLarge">Pointer to an array of icon handles that receives handles to the large icons extracted from the file. If this parameter is NULL, no large icons are extracted from the file. </param>
        /// <param name="phiconSmall"></param>
        /// <param name="nIcons">[in] Specifies the number of icons to extract from the file. </param>
        /// <returns>当获取数目时,返回包含的图标数目,其他情况返回一共导出了多少个图标</returns>
        [DllImport("shell32.dll")]
        public static extern uint ExtractIconEx(
            string lpszFile, 
            int nIconIndex, 
            IntPtr[] phiconLarge,    //[out]
            IntPtr[] phiconSmall,    //[out]
            uint nIcons
            );

这里再次强调一点:导出的图标是非托管资源,并且操作系统要求开发者必须自己调用destroyicon释放它们。所以实际上我们把它们到托管环境中创建一个克隆的对象,这样我们就能够马上释放掉它们。

核心功能介绍完,我们再看界面部分需要额外讲解的部分。我使用ListView显示图标,最初我把icons添加到listview的LargeImageList,但是这样以后的绘制图标被当作image,并且缩放到统一的尺寸,导致显示出黑色锯齿,失去了图标的精美设计外观。所以我考虑根本原因在于绘制时使用了DrawImage,而不是DrawIcon,导致外观异样。所以我决定改变ListView默认的绘制。所以我定义了IconListView类,从ListView继承。由于.net2.0增加泛型,所以我们可以很方便给我们自定义的iconlistview增加一个iconlist成员:
  /// <summary>
  /// 用大图标数组代替ImageList对象,以防止图标产生毛边!
  /// </summary>
  private List<Icon> m_IconList;

注意:改变默认绘制时必须设置ListView.OwnerDraw=true,否则系统忽略你的覆盖方法。

修改方法和修改ComboBox的绘制行为非常类似。(不同处在于在ListView中我没有发现可以复写的OnMeasureItem方法)

        /// <summary>
        /// 覆盖绘制Item方法!
        /// DrawItem Event: Occurs when a ListView is drawn and the OwnerDraw property is set to true. ***
        /// 注意e.Bounds已经刨除了Text区域,所以不要在这里试图绘制Item文本
        /// </summary>
        /// <param name="e"></param>
        protected override void OnDrawItem(DrawListViewItemEventArgs e)
        {
            if (this.View == View.LargeIcon)
            {
                ListViewItem item = e.Item;
                //如果没有图标,则采用默认行为绘制!
                if (this.m_IconList == null)
                    return;
                //绘制图标的横坐标位置!
                int x_icon = e.Bounds.Left + e.Bounds.Width / 2 - 16;
                //当item被选中时绘制背景
                if (item.Selected)
                {
                    this.m_Brush.Color = Color.FromKnownColor(KnownColor.Desktop);
                    e.Graphics.FillRectangle(this.m_Brush, e.Bounds);    //图标背景32*32
                }

                //绘制图标
                e.Graphics.DrawIcon(this.m_IconList[e.ItemIndex], x_icon, e.Bounds.Top);
            }
            else
                base.OnDrawItem(e);
        }

最后要注意的是,在OnDrawItem的参数e中的Bounds指的是什么,指的是Item在整个容器控件中的占据矩形(容器客户去坐标),所以它的Top会随着换行而递增。
同时系统还刨除了用于绘制Text的区域。所以在Bounds下方还有一块矩形区域是用于显示Item的文本,我们无法在这里绘制内容。
由于没有测量Item的方法,所以要改变Item的Bounds大小时比较费力(没有找到适合的属性或方法),这里我使用设置LargeImageList的ImageSize属性来影响Item的Bounds大小。

另外一点是绘制时我们必须还要注意item.selected属性对外观的影响,给用户提供足够明显的视觉反馈。

---------------------------------------------------------------------------------------------
在经过以上过程以后,正当我对自己所做的一切感到满意时,忽然发现一个非常严重的问题,.net类库中的image.save方法保存icon格式时,竟然是假的!因为当选择imageformat为icon时,从文件头的标志位可以看出实际保存成的格式是png格式!(只要吧后缀改成png即可打开)。而用icon.save(stream)的方法,又会丢失真彩色。(据我根据后来的努力分析,也许因为icon.save使用索引格式(16色或者256色)保存而非32bpp的格式,仅仅猜测)。

因此现在我们能正确显示出图标了,但是却无法保存为有效的ico文件!这个麻烦可就大了!所以我花了很多时间来分析ico文件格式,参考了很多网上资料,遗憾的是基本上没什么特别完整的资料。这里,我主要参考了微软的iconpro工具的源码,以及以下文档:
 文档1:
Icons in Win32
 文档2:(我发表完这篇日志才发现这个文档的内容和工作几乎和我做的几乎一样。。。!@#¥%……&)
Icon Browser: An Exercise in Resource Management

同时也用ultraedit来结合实际有效的ico文件的字节内容进行辅助分析。

终于对ico文件格式有了初步的认识,并且也保存成为有效的ico文件了。这里对ico文件格式不做具体说明,因为细节太多和繁杂,不是短小篇幅可以覆盖的。也不是这篇文章的重点。仅仅就简单说说比较重要的几点:

下图是一个16*16像素,32bpp的ICO文件(仅包含一个image)的字节数据:浅黄色背景的16个字节就是第一个Dir Entry。由于只有一个entry,所以它后面紧跟的就是第一个ImageData的BitmapInfoHeader结构(40 bytes)。文件中不包含RGBQUAD(色板)部分。BitmapInfoHeader后面直接就是XOR节(像素实际颜色,由于bpp=32,所以每个像素占据4个字节,注意在扫描行存储顺序是从图像最下方一行开始,向上方到顶部结束)。最后是AND Mask段(指示图标的透明部分)。

1.icon文件的格式是:
@ico File Header(可把前6个字节看作文件头)
    reserved:(0x00,0x00) 
    iconType:(0x01,0x00) 
    imageCount:
@dir entry数组
    entry[0],                    //16 bytes each entry 
    entry[1],
    ...
    entry[imageCount-1]
    dir entry可以看作是目录,每个条目中包含有指向Imagedata文件地址。

@Image Data数组
    imageData[0]
          BitmapInfoHeader      //size=40,width=宽度,height=2*高度,colorCount,planes=1,bitCount=(bpp)。其他为0.      
          RGBQUAD数组            //自带palette
               RGB[0]            //4 bytes each color,RGB加一个保留位 
               RGB[1]
               ...
               RGB[colorCount-1]
          XOR bytes             //像素数据,索引或者实际颜色数据
          AND mask bytes        //1bpp(和image一样大的1位深位图(只有黑白两色))?
    imageData[1]
    imageData[2]
    ...
    imageData[imageCount-1]
    image data是图像,有bitmap info header, RGBQUAD, XOR section, AND mask section组成。

2.
bitmap info header中,比较特殊的是height为图标高度的2倍(xor mask图高度+and mask图高度)。这里可以看到在逻辑上image的xor mask部分和and mask部分是上下排列的,因此宽度是image原始宽度,高度是2倍高度。如下图所示:在XOR部分显示出原始图像数据,可以理解为Photoshop中的一个普通RGB图层(layer),在AND mask部分可简单理解为在Photoshop中的应用在该图层上的蒙版(mask)概念。
                      
在系统绘制icon时,先用AND Mask图像用与操作复制到屏幕上,可以从上图中看出,这时图标透明像素(mask中的白色部分)不受影响,而不透明部分都变成0(黑色),然后再用XOR mask图像用异或操作复制到屏幕,异或的特点是相同为0,相异为1,
0 ^ 0 =0
0 ^ 1 =1
1 ^ 1 =0
因此,XOR 图像的黑色部分将保留现有颜色,即实现了透明效果,而不透明部分和0异或,因此这部分XOR图像保持原来颜色被复制到屏幕。

RGBQUAD是色板,它的颜色个数由dir entry中的bColorCount指定,当为0时表示没有色板(例如bpp为32的真彩色图标),图标类似无压缩的BMP位图,每个像素的数据表示实际颜色。不为0时表示具有色板,这时图标实际是索引图像,像素数据是一个索引。色板中每个颜色4字节,分别是R,G,B,0.

XOR section:它是实际的像素数据。在32bpp的图标中就是类似位图的实际颜色数据。在索引格式时,是一个在色板中的颜色索引。像素的bpp(bits per pixel)由dir entry以及bitmap info header中的biBitCount指定。(这两者都包含同一bpp信息,是冗余的。)

AND mask section的bpp文档中说为1,如果为1则图标的该像素位置透明,为0表示不透明。

  =====================================================================

  以下补充:由 hoodlum1980 于 2010-8-23 16:36:22 提供。

  =====================================================================
  【关于声明1的补充】在本文开头,我曾经补充一个说明,因为那时由于对ICO文件格式研究的不够充分,所以写ICO文件的代码存在一定缺陷和错误,所以保存后的图标在VS中打开看是有问题的。两年后我再次重新认真研究了以下ICO文件格式。应该说由于没有比较详尽可靠的官方文档,所以要研究ICO文件格式是难度较大的,非常消耗精力。现在我终于修正了写ICO文件中存在的问题,并把注意点补充如下:

 

  (1)ico entry 中的dwBytesInRes 指的是图片数据的大小。包括一个BitmapInfoHeader(40 bytes),Palatte(调色板,如果有,我们是真彩色图标,没有调色板,256色,16色的图片通常会有调色板。),位图数据(图标的XOR MASK部分,对于真彩色图标来说是24bpp,DWORD对齐),蒙版数据(图标的AND MASK部分,这里是1bpp,DWORD对齐)。写ico entry的代码如下:

  这里的大小不能计算错误。(在声明1中存在的问题之一是这里的计算有误)。

代码_ico_entry

private static void WriteDirEntry(Bitmap bm, BinaryWriter bw)
{
    //BitmapData bmData1 = bm.LockBits(new Rectangle(0, 0, bm.Width, bm.Height), ImageLockMode.ReadOnly, PixelFormat.Format32bppArgb);    //每个像素占3个字节(24位)
    
    int bpp1 = 24; //真彩色位图的bpp
    int bpp2 = 1;  //蒙版的bpp
    int stride1 = (bm.Width * bpp1 + 31) / 32 * 4;
    int stride2 = (bm.Width * bpp2 + 31) / 32 * 4;

    byte bwidth = (byte)bm.Width;
    byte bheight = (byte)bm.Height;
    byte bcolorcount = (byte)0;    //对于真彩图标32bpp来说,这个字段为0!
    byte breserved = (byte)0;
    Int16 wplanes = 1;
    Int16 wbitcount = (Int16)bpp1;        //8bit per pixel, 16 colors!
    Int32 dwbytesInRes = 40 + (stride1 + stride2)*bm.Height;   //40是BitmapInfoHeader的尺寸
    Int32 dwImageOffset = 22;    //???紧跟着就是第一个图像的入口!就是下一个字节的文件地址 6bytes header+ 16bytes entry

    //解锁
    //bm.UnlockBits(bmData1);

    //写入
    bw.Write(bwidth);
    bw.Write(bheight);
    bw.Write(bcolorcount);
    bw.Write(breserved);
    bw.Write(wplanes);
    bw.Write(wbitcount);
    bw.Write(dwbytesInRes);
    bw.Write(dwImageOffset);

    IconFileHelper.WriteIconImage(bm,bw);
}

 

    (2)是AND MASK部分,即表示图标哪些部分是透明的。也是要已DWORD(32bits)对齐的。它的bpp是1,即每8个像素用一个字节来表示。因此它的扫描行宽度是: bm.Width * 1 + 31/32 * 4; 声明1中描述的问题之二,是对这部分数据我原来没有做DWORD对齐,所以也是有问题的。

    有关写入AND MASK的代码如下:

 

代码_写入ANDMASK

//写入AND Mask , 1 bpp
stride = (1 * bm.Width + 31) / 32 * 4; //and mask 以DWORD对齐后的扫描行宽度
byte[] line = new byte[stride];

for (int j = bm.Height-1; j >=0; j--)
{
    for (int i = 0; i < stride; i++)
        line[i] = 0;

    //处理当前扫描行
    for (int i = 0 ; i < bm.Width; i++)
    {
        Color color = bm.GetPixel(i, j);
        int colorsum = color.R + color.G + color.B;
        if (colorsum  == 0)
        {
            //在位图中是黑色说明该像素应该是透明的
            line[i/8] |= (byte)(1<< (7 - (i & 0x07))); //i&7: 相当于i%8
        }    
    }
    bw.Write(line, 0, stride);
}

 

 

    修正以上两个问题以后,导出的图标可以在IDE中打开,显示正常,可以正常使用了!

---------------------------------------------------------------------------------------------
最后是项目源代码(build by vs.net 2005 trial)的下载链接:(已经解决声明1中存在的问题)

http://files.cnblogs.com/hoodlum1980/IconExp.rar

时间: 2024-09-17 09:55:34

用最快速度编写和发布的图标导出工具v1.0版的相关文章

淘宝对外发布了手机淘宝4.0版客户端

1.手机淘宝4.0支持海外手机号注册 日前,淘宝对外发布了"手机淘宝4.0版"客户端,该版本最大的变化之一是简化了用户注册淘宝账号的流程,支持用户通过手机号申请和注册淘宝账号.值得注意的是,该版本除了支持国内用户外,还支持港.澳.台地区与部分海外用户. 2.淘宝.天猫年货交易量同比上升103.1% 淘宝官方数据平台淘宝指数的数据显示,截至1月14日,在淘宝.天猫两大电商平台上,年货的成交量指数环比上升82.7%,同比上升103.1%.1号店副总裁黄志雄表示,1号店2014年的年货销售与

网易发布最新的网易邮箱5.0版

网易公司日前在京发布最新的网易邮箱5.0版,新版以流畅快捷的极速体验.简约清新的操作界面.随手可及的个性操控.精心设计的多元智能和一触即发的灵敏感应,对邮箱进行了重新定义,其写信语音输入.无障碍设计.邮箱触点.情感化设计等多项功能均是业界首创. 据了解,网易最新发布的邮箱5.0版采用网易自主研发的第五代引擎,通过持续优化通讯协议,让邮件到达更迅速更轻巧.与上一版本相比,新版本更新的功能点超过百个,其中数十个都是网易首创并独家拥有.这些新功能的增加,不但让网易邮箱犹如长了耳朵和眼睛,而且拥有了神经

[发布] Photoshop ICO 文件格式插件 V2.0版

[更新预告] 我正在开发 ICO 插件的 3.0 版本.增加了新的比原来更好的缩放算法.同时将改进一些 UI.预计 2017 年 6 ~ 7 月可以发布.2017 年 6 月 11 日. [声明]此插件下载链接已经修复,免费提供.  -- hoodlum1980,on 2011-3-23.   经过了少许努力,现在我将能够发布给 Photoshop 使用的 ICO 文件格式插件 2.0 版. 该插件 2.0 版本主要新增功能是,支持从 PE 文件(DLL,OCX)导入图标的功能.但是由于 Pho

CYQ.DBImport 数据库反向工程及批量导数据库工具 V1.0 发布

[Tip:2011-05-19 14:55左右修正个别Bug后重新上传了一下,之前下载的新重新下载.]   杂七几句: 自从购买VPS之后,打算将 秋色园QBlog 搬迁,也想把目前的Access数据库换成其它数据库. 由于VPS只有512M内存,装完系统都快300M,跑MSSQL2005太吃力,所以就不装了. 刚好系统默认装了MySql,于是就打算在MySql中跑一下.   秋色园QBlog 一开始就用上了CYQ.Data 的多数据库解析标签,能解析各数据库的差异化代码或函数. 所以更换数据库

sqlserver 批量数据替换助手V1.0版发布_实用技巧

这种方法操作繁琐,而且一般不是很懂数据库的人很难操作.于萌发了要写一个小程序的念头,经过两天时间的折腾这个小软件终于和各位见面了,希望各位童鞋多给点意见.说了这么些之后还是先上界面吧,^..^ 现在就来说说这个小程序的开发思路吧.第一步:通过 sp_helpdb系统存储过程得到SqlServer中的所有数据库名称. 复制代码 代码如下: #region 测试数据库连接,并显示数据库列表 /// <summary> /// 测试数据库连接,并显示数据库列表 /// </summary>

CYQ.IISLogViewer 一款IIS 日志分析工具 V1.0 发布[提供源码]

说几句:      昨天在 秋色开源团队  群里和网友聊天,有网友提到了一个概念,做站需要知道的:分析IIS日志.      然后上网找了一下资料看了下,可是 秋色园 寄放在人家虚拟目录的子目录中,根本没有IIS日志可言,于是昨晚就直接把秋色园移往新购买的VPS,正式搬到传说中的赌城"拉斯维加斯"去了,中间出了不少问题,折腾到夜里4点.      于是目前 秋色园 所在的地址就是"拉斯维加斯"了,不过数据库仍是用的Access. 下面进正题,于是自己跑IIS看了一下

网易公司在京发布网易邮箱5.0版,活跃用户数超过1亿

网易公司重新定义邮箱 网易最新发布的邮箱5.0版是完全基于HTML5和CSS3开发的邮箱,采用网易自主研发的第五代AJAX引擎.OPOA框架和完善的自定义组件库,通过持续优化通讯协议,让邮件到达更迅速更轻巧. 与上一版本相比,网易邮箱5.0版更新的功能点超过百个,其中数十个都是网易首创并独家拥有,包括个性化的多标签窗口.邮箱触点.写信语音输入.无障碍设计.情感化设计和情景模式等等.这些新功能的增加,不但让网易邮箱犹如长了耳朵和眼睛,而且拥有了神经中枢,用户发出的任何指令都可以一触即达,最让人惊喜

准备进入ReRAM速度!Crossbar发布SMIC芯片样品

16纳米?再低.10纳米?再低.Crossbar在制程方面表现出超强的野心. ReRAM初创企业Crossbar公司已经发布了一款来自SMIC的嵌入式ReRAM芯片样品,且其目前正在接受评估. SMIC目前正在采用40纳米制程,且有计划进一步开发28纳米制程工艺.但Crossbar公司的设想是至少要将其控制在16纳米到10纳米水平,且随后还要进一步实现缩小. 这款芯片设计方案采用非导电非晶硅(简称a-SI)技术.顶部与底部电极之间存在开关层,且该层相对于通过电流的电阻基于离子(银)金属运动.当在

传百度拟推类Siri语音助手 最快将于圣诞节发布

12月25日消息 据知情人士透露,百度最快将于今日发布类http://www.aliyun.com/zixun/aggregation/35389.html">苹果Siri中文语音软件服务"百度语音助手",将先在Android平台推出. 百度移动产品设计师骆旭剑透露,语音助手早在上个月就已开发完成,将于本周推出,最快今日就能上市,所使用的语音识别技术是自主研发的深度神经网络识别引擎,整合了百度NLP技术,综合识别准确率可提升8%. 据了解,百度语音助手融合了百度框计算技