软件的永恒之道

隔了这么长时间,再重新捡起Alex的书来看,感觉有点脱节了:P

Alex说:各种模式是相互关联的,形成了模式语言。–这个好像前两篇都有提到:)

但是当使用了模式语言构造出建筑以后,就形成了一个各部分相互依存的“系统”。软件亦是如此。

正如前面所说,模式有好坏之分,如果这个系统中存在着坏的模式(我前面说过,模式的好坏是相对的,不是绝对的,一个好的模式,如果用的不是地方,同样会变成一个坏模式)。对于建筑来说,我们可以说,这个系统存在着应力(印象中这个术语好像是来自“结构力学”,用于说明材料内部用于平衡外力的内力)。对于软件来说,我们就会说:这个软件有Bad smell。

正如当材料中的应力超过一定程度后,会对材料造成破坏。软件中的Bad smell同样有这样的效果。如果说有什么不同的话,应力造成的破坏不一定都是坏的,它也可能使系统达到另外一个平衡,并最终消除应力。但软件中的Bad smell好像没有这样的效果。

这个时候,我们需要“重构”。–看来这是一段通往“软件的永恒之道”了^_^

然而要注意到:一个系统中的各个部分都是相互依赖而又相互影响的。在这样的系统中,存在着“牵一发而动全身”的情况。其表现可以用“蝴蝶效应”来形容。


1979年12月,洛伦兹在华盛顿的美国科学促进会的一次讲演中提出:一只蝴蝶在巴西扇动翅膀,有可能会在美国的德克萨斯引起一场龙卷风。他的演讲和结论给人们留下了极其深刻的印象。从此以后,所谓“蝴蝶效应”之说就不胫而走,名声远扬了。


以上就是关于“蝴蝶效应”的简单说明(注:其中的洛伦兹是混沌动力学家、气象学家L.A.洛伦兹,不是那个爱因斯坦时代的物理学家H.A.洛伦兹[1853-1928])。BTW:最近还有一部以此为题的电影,还不错,顺便推荐一下。^_^

为了防止“蝴蝶效应”的不良影响,所以我们还需要:“小步迭代”和“配置管理”。–这也可以说是通向“软件的永恒之道”的重要一段。

重大好消息居然这么不起眼

昨天看新闻联播,有一条很重要的新闻:

证监会批准深交所开设中小企业板,也就是传闻了至少两三年的所谓二板市场。

前两年炒得沸沸扬扬,仿佛过几天就能设立似的,结果一拖就是几年。这一两年已经没有人提这个话题了,却突然暴出这么个消息,真让人有点措手不及。

不过就是这么一条重要的好消息,居然在新闻联播里是作为简讯,一句话就带过了。以至于在看完天气预报后,我都忘记这条新闻的内容,只记得好像有一则重要消息,但就是想不起是什么来。

直至今天早上看早新闻才又看到它。

不得不说这个好消息来得太晚了,不过也许这未必不是好事。

就在现在TOP之流在主板骗钱的伪高科技企业的倒下后开辟这样一个二板市场,有助于投资者更冷静和理性地看待所谓的高科技企业,让资金能有更准确的流向,去支持那些真正有价值的高科技企业,有效地降低二板市场的投资风险。

设想一下,如果在两三年设立二板,像TOP这样的企业会有多少,现在损失又会有多大。

所以,在这一点上,不论对那些真正有前途的中小高科技企业来说,还是对投资者–特别是中小投资者、散户等–来说,都是有极大的好处。

帮南南贴照片


波罗的海落日


赫尔辛基大教堂


赫尔辛基歌剧院


赫尔辛基教堂广场


赫尔辛基教堂广场纪念碑


火车站雕塑


进关难,出关更难


我们乘坐的游轮VIKING LINE


西贝柳斯纪念碑1


西贝柳斯纪念碑2


岩石教堂的管风琴


沿途风光

关于《隆重推出偶MM在法国拍的PP》的说明

见我五月十日的BLOG《隆重推出偶MM在法国拍的PP》

她那次去的那个地方叫做“未来世界”,所以那些建筑都是很有未来气息的。

那些个球形建筑里面是一些展馆,比如其中一个里面是星空展示馆,在里面可以从360度角观看整个夜空(当然是模拟出来)。

还有其它一些展馆如三维演示馆,在其中可以看到非常逼真的三维视觉体验。比如看到一条金鱼从你身边游过,你甚至会有伸手去抓它的尾巴的想法,不过当然是只能抓到空气了:P

据偶MM说,她那天在那里玩了一天,一大早就去了,玩到晚上七八点还不想走,而且门票也不贵,只要二十欧元。

55555555555,可惜我去不了:(

BTW:最近国内在搞科技周活动,昨天在新闻上看到上海科技馆也有一些类似的节目(比如三维金鱼),有时间可以去看看。

软件与经济

刚才吃过午饭就看了一会电视,很久没有象样地看看电视了。
正好碰上方宏进主持的《中国经营者》,采访的对象是UT斯达康的吴鹰。
在谈到小灵通被认为是落后技术的问题时,吴鹰的回答我觉得很好:评价一个技术是先进还是落后,不应该看它是否前卫,因为历史上有太多的前卫技术最终归于消亡并且几乎没有留下什么痕迹,比如“铱星”;最关键的还是看它是不是降低了成本,创造了效益。
那些指责小灵通技术落后的人为什么不问问自己:为什么UT斯达康能凭着一项“落后”的PHS技术,取得了每年近60%的增长率?
归根结底还是取决于市场!
最后吴鹰说了:小灵通最终是一定会被淘汰的,但它具有的历史意义始终存在,那就是–它是中国电信史上第一项被市场选择的技术,而不是被政策所选择的。
软件业其实也一样,我们常常会被大公司层出不穷的新技术引得团团转,按孟岩兄的说法就是:被不断地反复蹂躏。
事实上很多人却忽视了最关键和重要的一点:我们的用户其实最关心的并不是你用的什么技术,而是你的软件是否能解决他们的问题!
只有被市场所选择的技术,才是好的技术!
那么市场是什么?
大公司不可能是我们的市场。如果我们跟着大公司走,正是中了他们的圈套,因为我们就是他们的市场。
要记住的是:我们的用户才是我们的市场!
如果不能意识到这一点,就只能跟在大公司的PG后面转,永远不会有大的成长。

没什么可写的就贴PP吧:)



传说中的“厦大”

南普陀

那天吃完活鱼在员当湖边拍的湖滨北路

中山公园

中山路(从文化宫方向)


大中路



从轮渡上看黄昏的鼓浪屿

厦鼓轮渡

鼓浪屿轮渡的海豚雕塑

鼓浪屿最高点:日光岩


菽庄花园外的海滩(人多垃圾就多啊,汗)

一轮满月照耀下的菽庄花园


夜之海


环岛路的灯火

厦门之夜

轮渡的标志性建筑

老字号黄则和花生汤

熙熙攘攘的定安夜市

EXIF格式分析及通过XML处理

EXIF格式分析及通过XML处理

 

猛禽[Mental Studio](个人专栏)(BLOG)

http://mental.8gua.me

 

随着数码相机的普及,EXIF已经被大多数图像处理软件所支持。虽然我做的是一个小玩意儿(见《人个信息助理之我的相册》)但毕竟也是用于图像处理的,虽然目前支持JPEG文件格式,但是还不支持EXIF

那么,什么是EXIF呢?EXIFExchangeable image file format的缩写,即“可交换图像文件格式”,它是由日本电子与信息技术工业协会(JEITA)所制定的一项标准,用于实现在不同的软件或设备之间交流图像数据,典型的应用就是数码相机直接连接打印机打印照片。当然,EXIF中还包含了很丰富的信息,从中可以知道这个数码照片是用什么相机拍的,拍摄时用的光圈、速度、ISO等。而且最新版本的EXIF还支持音频格式文件。

关于EXIF的最权威文档资料当然是JEITA的标准规范[1],目前最新的版本是2.2。不过JEITA的网站上虽然提供了两个语言版本(日语和英语,并且JEITA声明以日文版为准)的规范文档,但是需要收费的。还好通过GOOGLE还是找到了一个英文版的。

EXIF只提供对两种图像文件格式的支持:TIFF[2]JPEG[3,4]。其中对不压缩图像使用TIFF格式,对压缩图像使用JPEG格式。本文主要讨论JPEG格式。

我们知道JPEG文件格式是通过所谓的Marker Segments来记录图像的相关信息的,这种方式具有非常好的灵活性和可扩充性,较之早年的PCXGIFBMP等采用固定格式文件头记录的方式要好很多(PCX原先是为16色图像设计的,在256色图像出现后,就破坏了原先的格式定义,将调色板续在文件尾部;而GIF虽然内部也有分段机制,后来被扩充为实现动画功能,但仍然是采用固定格式的文件头记录基本信息),而EXIF就是利用了这一点。

JPEG文件中的每一个Marker Segments都是以一个WORD类型的数值开始(注意:这个数值记录在文件中时是高位字节在前,低位字节在后,将在后面介绍这个字节顺序的问题),这个数值即所谓的Marker,每个Marker代表着相应的Segment的意义,如果这个Segment有内容(即长度大于0,是否有内容视具体Marker而定),接下来的一个WORD类型的数值就是这个Segment的长度(这个数值的字节顺序与Marker相同),至于Segment的具体内容,则根据Marker的不同有不同的定义。如FFD8这个Marker叫做SOI,表示图像的开始,这个段是没有内容的;如FFE0则是APP0,即应用程序段0,属于可自定义的数据,它已经被用于JFIF[4],这个段则是有内容的,接下来的一个WORD就是段长度,段内容的定义是由JFIF规范所定义。

EXIF也是一种扩展定义,类似于JFIF,它使用了APP1APP2这两个Marker Segments。之所以要用两个Marker是因为如前面所说,Segment的长度是用一个WORD来表示,即最大不超过64K。因为EXIF支持一种被称为Flashpix的无损图像格式,其数据很可能超过64K,所以用了APP2,其中APP2可以有多个,不过因为对Flashpix的支持属于EXIF的扩展功能(在规范文档的附录F中说明[1]),通常很少用到,本文不作讨论。

EXIF定义的APP1段是一个标准的JPEG Marker Segment,如表1所示。其中的APP1 Marker的值为FFE1Length为这个段的长度,其值包括Length本身所占的两个字节,但不包括Marker所占的两个字节。段中剩下的部分便是EXIF数据。

EXIF数据的格式定义也很简单,如表2所示。它包括两个部分:EXIF< span>头和TIFF头。EXIF头由六个字节组成,其内容为一个长度为4ASCIIZ(以NULL结尾的ASCII)字符串,加一个字节的0(用于使数据按WORD对齐),而这个ASCIIZ串内容就是“Exif”。而TIFF头则是采用了标准的TIFF文件格式的定义(TIFF同样是一种定义灵活的文件格式,在某种程度上说是太灵活了),这样可以让JPEGTIFF两种格式中的EXIF信息可以以一致的方法进行处理。

 

起始

长度(Bytes)

内容

0x00

2

APP1 Marker(0xFFE1)

0x02

2

Length

0x04

Length – 2

EXIF Data

1APP1段格式定义

 

起始

长度(Bytes)

内容

0x00

6

EXIF Header

0x06

APP1 Length – 8

TIFF Header

2EXIF格式定义

 

起始

长度(Bytes)

内容

0x00

2

Byte order

0x02

2

Flag(0x2A)

0x04

4

The offset of the first IFD

3TIFF Image File Header格式定义

 

TIFF Header[2]包括两个部分:Image File HeaderIFDImage File Directory)链表。其中Image File Header的定义如表3所示。其中Byte order用于说明此TIFF文件所采用的字节顺序,用两个字符表示,有两种选择,分别是:IIMM(这个MM跟美眉无关J),其中II是指采用Intel字节顺序,而MM是指采用Motolora字节顺序(见下面的说明)。FlagTIFF文件格式的标志,总是为0x002A,即十进制数42。最后一个DWORD是指向第一个IFD的起始位置,其偏移量的计算起点是TIFF Header的起点,即如果第一个IFD是紧接着Image File Header的话,这一项的值就为8Image File Header的大小)。

 

关于字节顺序的说明:

字节顺序是可交换文件格式中,特别需要注意的一个问题。所谓“可交换文件格式”就是说这种文件格式可以在各种不同的软硬件平台下被正确地解读。字节顺序问题的起因在于硬件上。

CPU发展的早期(8CPU的时代),由于指令集的丰富,许多8CPU都可以处理16位数据,当然都是分两次进行的,这时就出现的字节顺序的问题:是先处理高位字节还是先处理低位字节?不同的CPU厂商采用不同的选择!以Intel, Zilog等公司为代表的CPU厂商是采用先低后高的方式,即低位地址保存低位字节的数据;
而以
Motolora(它可不止是做手机,它曾经是世界上最大的电子产品制造商)则是采用先高后低的方式,与通常人的阅读顺序一致。对应的硬件就是采用Intel架构的IBM PC及其兼容机上运行的软件都是采用Intel顺序的,而采用由IBMMotoloraApple共同设计的Power PC芯片的Apple Mac则是采用Motolora顺序的。

现在,字节顺序问题不只出现在图像格式上,由于Unicode字符集(UCS)也是采用了16位(UCS-2)或32位(UCS-4)来表示一个字符,所以也面临着字节顺序的问题。

另外,按照各自字节顺序的特点,Intel的字节顺序也叫做little-endian,而Motolora的字节顺序就叫做big-endian

 

1IFD链表结构

 

IFD是一个链表结构,如图1所示,在每个IFD的末尾包含一个指向下一个IFD的偏移量(同样是从TIFF Header算起),如果这个偏移量为0,则表示已经到了链表的末尾。EXIF只使用了两个TIFF IFD,分别被称作IFD0IFD1,但定义了三个自己的IFDEXIF IFD, GPS IFD, Interoperability IFD,它们的结构与标准TIFF IFD相同,但不是记录于TIFFIFD链表中,而是作为IFD0的扩展记录的。

 

起始

长度(Bytes)

内容

0x00

2

Number of Directory Entries(Count)

0x02

12 * Count

Directory Entries

2 + 12 * Count

4

Offset of next IFD

4IFD格式定义

 

每个IFD由三个部分组成,如表4所示,包括:Number of Directory EntriesDirectory EntriesOffset of next IFD。其中Number of Directory Entries指定在Directory Entries中包含多少个EntryDirectory Entries是一个数组,包含若干个Directory Entry。最后的Offset of next IFD即是下个IFD所在的位置,如果此项为0,则表示这是链表中的最后一个IFD

 

起始

长度(Bytes)

内容

0x00

2

Tag

0x02

2

Type

0x04

4

Size

0x08

4

Value

5IFD Entry格式定义

 

IFD Entry是一个12字节长的结构,如表5所示。正如TIFF的名称所说的那样:A tag-based file format for storing and interchanging raster images[2]。所有的IFD Entry都是通过Tag来标识的,每一个Tag都是一个WORD类型的数值,每个数值有其特定的含义。如0x0131这个Tag表示此Entry记录的是生成此TIFF文件的软件名等。具体每个Tag的含义可能查阅TIFF的规范文档[2]EXIF只用到了其中部分Tag,另外还扩充了三个Tag用于链接EXIF的三个扩充IFD,这些在EXIF的规范文档中有说明[1]

IFD Entry中的Type是指明此Entry中记录的数据类型,TIFF规范只定义了五种类型,EXIF增加了三种。各类型说明如表6所示:

 

Type

类型

Size

Value

1

BYTE

1

字节数据,Size一般为1

如果Size大于4,则Value为其位置

2

ASCII

n

一个ASCIIZ的字符串,Size为串长度,包括结尾的NULL字符

Size小于等于4则直接存放在Value

Size大于4,则在Value中指定其位置

3

SHORT

1

无符号短整数,Size一般也为1

如果Size大于2,则Value为其位置

4

LONG

1

无符号长整数,Size一般也是1

5

RATIONAL

1

有理数,TIFF是用分数的形式来表达,用了两个LONG类型的数据,前一个LONG为分子,后一个LONG为分母,Size一般也是1

因为一个RATIONAL类型包含两个LONG,无法记录在Value中,所以Value中记录的是这个RATIONAL数所在的位置(从TIFF Header开始的偏移)

以上为TIFF定义的类型,以下为EXIF扩展定义类型

7

UNDEFINED

n

任意的字节数据,根据具体情况定义

Size小于等于4则直接存放在Value

Size大于4,则在Value中指定其位置

9

SLONG

1

有符号长整数,与LONG类似,以2的补码形式表示

10

SRATIONAL

1

有符号有理数,与RATIONAL类似,不过是用两个SLONG来表示

6Type定义

 

关于Value的内容有一点要注意的是,它可能是数据本身,也可能是数据存放位置的偏移,这取决于TypeSize的大小。数据存放位置都是从TIFF Header开始计算的偏移量。

有一点要注意的是:EXIF的三个扩充IFD Tag也是LONG类型,它记录的是相应IFD的起始位置(从TIFF Header开始的偏移)。在扩充IFD中用到的Tag全部是EXIF重新定义的。

下面是一个典型的EXIF JPEG文件格式分析结果(源文件为一张用Nikon CoolPixel 775相机拍摄的照片,所用的EXIF版本是2.1,与2.2版差别不大):

 

JPEG SOI : FF D8  //  图片起始

JPEG APP1: FF E1

  APP1 Size : 1C 45  //  注意:前面这三个WORD都是big endian

  EXIF Flag : ‘Exif’, 0, 0

  TIFF Header:

    Byte Order: ‘II’

    Flag      : 2A 00

    IFD0 offset : 08 00 00 00

      Entries Count : 0B 00 // 11

      IFD Entry :

          Tag    : 0E 01  //  Image Description 图像说明

          Type   : 02 00  //  ASCII

          Size  : 0B 00 00 00

          Value : 92 00 00 00  //  from TIFF Header

      IFD Entry:

          Tag    : 0F 01  //  Make 制造

          Type   : 02 00

          Size  : 06 00 00 00

          Value: B2 00 00 00

      IFD Entry:

          Tag    : 10 01  //  Model 型号

          Type   : 02 00

          Size  : 05 00 00 00

          Value: CA 00 00 00

       

      IFD Entry:

          Tag    : 69 87  //  EXIF IFD

          Type   : 04 00  //  LONG

          Size  : 01 00 00 00

          Value: 1C 01 00 00  //  Offset of EXIF IFD

    END of IFD0

    IFD1 Offset : 18 03 00 00

      //  存放IFDValue数据

    EXIF IFD :

      Entries Count : 18 00

      IFD Entry :

          Tag    : 9A 82  //  Exposure time

         

    END of EXIF IFD

    Next IFD : 00 00 00 00  //  按标准IFD链表约定,表示没有后继IFD

      //  存放EXIF IFDValue数据

    IFD1 :  //  EXIF中用于存放缩略图

      Entries Count : 06 00

      IFD Entry :

          Tag    : 03 01

         

    END of IFD1

    Next IFD : 00 00 00 00  //  EXIF只用到两个TIFF IFD

    … //  Thumbnail etc.

  //  end of TIFF header

  //  其它JPEG Marker segments

JPEG EOI : FF D9  //  图片结束

 

EXIF格式的分析,至此基本上告一个段落了。从分析结果上可以看出,EXIF是一种非常灵活的格式,具有非常好的可扩充性,要想较好地处理其中的相关数据也是比较麻烦的。

其困难主要在于几个方面:

1、对于每种不同的IFD Entry Type,需要用不同的方法获取数据,特别是对于数据长度不同时,可能采用不同的数据存储方式,而IFD Entry的数量又可能很多,每个Entry根据Tag不同又有不同的意义

2、EXIF IFD是作为TIFF IFD的子链表形式存在(因为EXIFIFD里定义了不同于TIFF标准的Tag,要保持与标准的TIFF格式互用,必须这样做),使得原来的链表结构变成了树形结构

3、Tag的种类和数量非常之大,在EXIF规范里定义了各个Tag的支持级别(见[1]4.6.8),光是JPEG格式下必须支持的Tag就有十几个,TIFF格式更多,再加上可选支持的Tag,有几十上百个,并且还存在未来继续扩充的可能

4、对于可支持不同语言的软件来说,同一个Tag的意义要用每一种支持的语言表达一次,如果将这部分处理写入代码,对于增加新的语言支持会带来不必要的麻烦

为了解决这些困难,必须要找到一个同样是非常灵活的处理方法来处理EXIF数据。而XML正是这样一种方法。从前面的分析结果可以看出,EXIF的数据记录方式是层层嵌套的树形结构,是非常适合用XML的,因为XML也是这样的树形结构。

通过定义一套XML标签,然后将EXIF数据转换成XML文档,可以最大限度地保留EXIF数据的原始内容及结构。并且作为一种通用格式,XML可以很方便地进行再处理,比如:通过XSLT进行转换,使之成为HTML或其它便于显示的格式;或者将此XML传递给其它软件作进一步处理等。

对于前面说到的困难,XML都很好地解决:

1、不同类型的问题,通过转换为XML,将所有的Value都转换成字符串,便于统一处理

2、XML本来就是树形结构,可以在转换的时候方便地通过调整节点位置,使各IFD统一处理

3、可以将所有的Tag原样导出到XML中,在以后对XML的处理时再根据Tag进行处理,比如通过修改XSL文件实现对新增Tag的支持

4、同样是对XML处理时才需要面对具体的Tag,比如为不同的语言提供相应的XSL文件即可

下面的代码片断(Borland C++ Builder)实现了从EXIF数据到XML的转换:

//---------------------------------------------------------------------------#include typedef struct {WORD  EntryTag;WORD  EntryType;DWORD EntrySize;DWORD EntryValue;} TIFDEntry;#include //---------------------------------------------------------------------------BYTE * __fastcall TExifXML::GetIFD(_di_IXMLNode aNode, BYTE * aTIFFHeader, int aPosition, AnsiString aName){_di_IXMLNode pIFD = aNode->AddChild( "IFD" );if ( aName != "" )pIFD->Attributes["name"] = aName;BYTE * p = aTIFFHeader + aPosition;WORD nWord;memcpy( &nWord, p, sizeof ( nWord ) );p += sizeof ( nWord );_di_IXMLNode pChild = pIFD->AddChild( "Count" );pChild->Text = Format( "0x%X", ARRAYOFCONST( ( ( int )nWord ) ) );TIFDEntry ent;_di_IXMLNode pEntry;BYTE * pTemp;for ( int i = nWord; i > 0; --i ){memcpy( &ent, p, sizeof ( ent ) );p += sizeof ( ent );pEntry = pIFD->AddChild( "Entry" );pChild = pEntry->AddChild( "Tag" );pChild->Text = Format( "0x%X", ARRAYOFCONST( ( ( int )ent.EntryTag ) ) );pChild = pEntry->AddChild( "Type" );pChild->Text = IntToStr( ent.EntryType );pChild = pEntry->AddChild( "Size" );pChild->Text = Format( "0x%X", ARRAYOFCONST( ( ( int )ent.EntrySize ) ) );pChild = pEntry->AddChild( "Value" );switch ( ent.EntryType ) {case 1 :  // BYTEif ( ent.EntrySize == 1 )pChild->Text = Format( "0x%.02X", ARRAYOFCONST( ( ( int )( BYTE )ent.EntryValue ) ) );elsethrow Exception( "Unsupported!" );break;case 2 :  // ASCIIif ( ent.EntrySize <= 4 )pChild->Text = reinterpret_cast( &ent.EntryValue );elsepChild->Text = reinterpret_cast( aTIFFHeader + ent.EntryValue );break;case 3 :  // SHORTif ( ent.EntrySize == 1 )pChild->Text = Format( "0x%.04X", ARRAYOFCONST( ( ( int )( WORD )ent.EntryValue ) ) );elsethrow Exception( "Unsupported!" );break;case 5 :  // RATIONALpChild->Text = FloatToStr( *reinterpret_cast( aTIFFHeader + ent.EntryValue )/ ( double )( *reinterpret_cast( aTIFFHeader + ent.EntryValue + sizeof ( DWORD ) ) ) );break;case 7 :  // UNDEFINEDif ( ent.EntrySize <= 4 )pTemp = reinterpret_cast( &ent.EntryValue );elsepTemp = aTIFFHeader + ent.EntryValue;pChild->Text = "";for ( int j = 0; j < ( int )ent.EntrySize; ++j ){pChild->Text = pChild->Text+ Format( " 0x%.02X", ARRAYOFCONST( ( ( int )( BYTE )( *pTemp ) ) ) );pTemp++;if ( j % 16 == 15 )pChild->Text = pChild->Text + "rn";}break;case 9 :  // SLONGif ( ent.EntrySize == 1 )pChild->Text = IntToStr( ent.EntryValue );elsethrow Exception( "Unsupported!" );break;case 10:  // SRATIONALpChild->Text = FloatToStr( *reinterpret_cast( aTIFFHeader + ent.EntryValue )/ ( double )( *reinterpret_cast( aTIFFHeader + ent.EntryValue + sizeof ( int ) ) ) );break;default:  //  LONG & other unknown typepChild->Text = Format( "0x%.08X", ARRAYOFCONST( ( ( int )ent.EntryValue ) ) );break;}switch ( ent.EntryTag ) {case 0x8769 :  //  Exif IFDGetIFD( aNode, aTIFFHeader, ent.EntryValue, "EXIF" );break;case 0x8805 :  //  GPS IFDGetIFD( aNode, aTIFFHeader, ent.EntryValue, "GPS" );break;case 0xA005 :  //  Interoperability IFDGetIFD( aNode, aTIFFHeader, ent.EntryValue, "InterOp" );break;}}return p;}//---------------------------------------------------------------------------void __fastcall TExifXML::GetTIFFHeader(_di_IXMLNode aNode, BYTE * aTIFFHeader){BYTE * p = aTIFFHeader;char sByteOrder[3];memcpy( sByteOrder, p, 2 );p += 2;sByteOrder[2] = 0;_di_IXMLNode pChild = aNode->AddChild( "ByteOrder" );pChild->Text = sByteOrder;WORD nFlag;memcpy( &nFlag, p, sizeof ( nFlag ) );p += sizeof ( nFlag );pChild = aNode->AddChild( "Flag" );pChild->Text = Format( "0x%.04X", ARRAYOFCONST( ( ( int )nFlag ) ) );DWORD nPointer;memcpy( &nPointer, p, sizeof ( nPointer ) );int i = 0;while ( nPointer > 0 ){p = GetIFD( aNode, aTIFFHeader, nPointer, AnsiString( "IFD" ) + IntToStr( i++ ) );if ( !p )break;memcpy( &nPointer, p, sizeof ( n
Pointer ) );}}//---------------------------------------------------------------------------int __fastcall TExifXML::LoadFromStream(TStream * aStream){if ( !FXMLDoc )throw Exception( "XMLDoc property is null!" );TMauto_ptr ms( new TMemoryStream( ) );ms->CopyFrom( aStream, aStream->Size );ms->Seek( 0, soFromBeginning );FXMLDoc->FileName = "";FXMLDoc->Active   = true;FXMLDoc->Version  = "1.0";FXMLDoc->Encoding = "GB2312";_di_IXMLNode pNode = FXMLDoc->AddChild( "ExifAPP1" );_di_IXMLNode pChild = pNode->AddChild( "ExifID" );char sExifID[6];ms->Read( sExifID, 6 );pChild->Text = sExifID;pChild = pNode->AddChild( "TIFFHeader" );BYTE * pHeader = static_cast( ms->Memory ) + ( int )ms->Position;GetTIFFHeader( pChild, pHeader );return ms->Size;}

其中FXMLDoc是一个TXMLDocument控件,用于生成XMLLoadFromStream方法读入的内容为JPEG APP1这个Marker Segment的内容(注意,不是JPEG文件)。GetTIFFHeader方法用于读出TIFFHeader的内容,包括Image File HeaderIFD链表。GetIFD则是用于解读IFD的具体内容,其中包括对EXIF的三个扩充IFD的递归解读,并且其中包含了将各种数据类型转换为字符串的部分,特别是对不定长的UNDEFINED类型的处理(其结果见下面转换后的XML)。

转换后的XML大致如下:

<?xml version="1.0" encoding="GB2312"?><ExifAPP1><ExifID>Exif</ExifID><TIFFHeader><ByteOrder>II</ByteOrder><Flag>0x002A</Flag><IFD name="IFD0"><Count>0xB</Count><Entry><Tag>0x10E</Tag><Type>2</Type><Size>0xB</Size><Value>          </Value></Entry><Entry><Tag>0x10F</Tag><Type>2</Type><Size>0x6</Size><Value>NIKON</Value></Entry><Entry><Tag>0x110</Tag><Type>2</Type><Size>0x5</Size><Value>E775</Value></Entry>...<Entry><Tag>0x8769</Tag><Type>4</Type><Size>0x1</Size><Value>0x0000011C</Value></Entry></IFD><IFD name="EXIF"><Count>0x18</Count>...<Entry><Tag>0x9000</Tag><Type>7</Type><Size>0x4</Size><Value> 0x30 0x32 0x31 0x30</Value></Entry>...<Entry><Tag>0x9286</Tag><Type>7</Type><Size>0x7D</Size><Value> 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x200x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20</Value></Entry>...<Entry><Tag>0xA005</Tag><Type>4</Type><Size>0x1</Size><Value>0x00000376</Value></Entry>...</IFD><IFD name="InterOp"><Count>0x2</Count><Entry><Tag>0x1</Tag><Type>2</Type><Size>0x4</Size><Value>R98</Value></Entry><Entry><Tag>0x2</Tag><Type>7</Type><Size>0x4</Size><Value> 0x30 0x31 0x30 0x30</Value></Entry></IFD><IFD name="IFD1"><Count>0x6</Count><Entry><Tag>0x103</Tag><Type>3</Type><Size>0x1</Size><Value>0x0006</Value></Entry>...</IFD></TIFFHeader></ExifAPP1>

有了这个XML就可以很方便地进行下一步处理了,比如用下面这个XSL文件对上面这个XML进行转换:

<?xml version="1.0" encoding="GB2312" ?><xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/TR/WD-xsl"><xsl:template match="/"><xsl:for-each select="ExifAPP1/TIFFHeader/IFD/Entry"><xsl:choose><xsl:when test="Tag[.='0x10F']">制造商=<xsl:value-of select="Value" /></xsl:when><xsl:when test="Tag[.='0x110']">型号=<xsl:value-of select="Value" /></xsl:when></xsl:choose></xsl:for-each></xsl:template></xsl:stylesheet>

即可得出下面的结果:

制造商=NIKON

型号=E775

以后不论是增加Tag还是要改语言,只要修改这个XSL文件即可实现,完全不用修改EXIF处理部分的程序代码,非常的灵活方便。

 

[Mental Studio]猛禽

2004-4-3

参考文献:

[1]      JEITA CP-3451. Exchangeable image file format for digital still cameras:Exif Version 2.2. JEITA(Japan Electronics & Information Technolog Industries Association). April, 2002.

[2]      Aldus Corporation. TIFF Revision 6.0 Final – June 3,1992.

[3]      Gregory K.Wallace. The JPEG Still Picture Compression Standard.Communications of the ACM. April, 1991.

[4]      Eric Hamilton. JPEG File Interchange Format Version 1.02. C-Cube Microsystem. September 1, 1992.

可怜的中国软件

今天刚写了一篇跟物理有关的BLOG,就在CSDN上看到这个:《[教育]尴尬的国际地位-可怜的中国物理!》

可怜的又何止物理呢?软件业不是一样,推而广之,整个中国计算机业也是。归根到底是整个中国的学术界都有问题。那些所谓的砖家,鞋者们,很多其实在真正的大师面前,是连给人提鞋也不配的。这不是我要妄自菲薄,实在是因为它们“学术腐败”。当然这其中教育要承担很大的责任,所谓上梁不正下梁歪,老师教授都不是靠真正的学术成果得到升迁或好的待遇,那学生自然要靠抄袭才能完成毕业论文了(单MOP上就常见有人散尽MP求论文的,CSDN上也有)。

不过真要深究下去就没底了,因为整个社会的道德水准都在沦丧,这一点只要每个周日看看中央台的《每周质量报告》应该就会深有体会了。

还是回到软件业上。中国自从有了所谓的软件行业,就始终是笼罩在一种急功近利的浮躁氛围中。这其中IT媒体对所谓的科技明星的炒作有一份很大的功劳。于是乎,听说Bill.Gates靠软件发大财了,就一窝蜂地去做软件;听说.com流行了,就一窝蜂地去Internet上淘金;听说JAVA不错,就全开始J2EE/EJB起来;听说.net要后来居上,就全都开始啃C#;听说印度人做软件不错,就排着队上西天取经去了;听说CMM/ISO 9000好,就一个个去过认证,最可笑的是好像很少有过不了的;听说RUP好,就都开始ROSE上了;听说XP不错,就一个个Agile起来……转眼二十几年过去了,MS从一个几十个人的小公司成为世界第一大软件公司,我们呢?什么也没有……不,还有巨人、科利华、TOP之类,都是“大”“软件”公司啊……

我们不是革命者,没有能力去改变这个社会,但至少我们能做到:踏实做好自己的事。浮躁之风,能少一点是一点。

高技术流氓

在偶的QQ里有几个技术群,比较寒的是,在这几个群里的家伙都知道偶是个老流氓。

不过很不幸的是,我昨天在C++NPv2(D.Schmidt的关于ACE的书的第二卷)的译序里看到马维达引用了Linus.Torvalds的一句名言:“Software is like sex: It’s better when it’s free!”原来越是高手就越是流氓啊。

大概这也是人之常情吧。英国著名物理学家Stephen.Hawking(《时间简史》的作者)就曾经因一个关于黑洞的问题和美国著名物理学家Kip.Thorn打赌:“史蒂芬·霍金:我和基帕·索恩打赌说,在天鹅座X-1中没有黑洞,这对我来说像是买保险。我为研究黑洞做了大量工作,如果黑洞不存在,我花的工夫就都白费了。所以,如果黑洞存在,基帕就会得到一年的《阁楼》。如果不存在,我将得到四年的《私人眼睛》作为安慰奖品。”–引自《时间简史》第三章,在Thorn的书《黑洞与时间弯曲》一书中也有讲到这次打赌。

而《阁楼》(或译得更直接一些叫《藏春阁》)和《私人眼睛》(或译作《私家侦探》)都是和《花花公子》类似的成人杂志:P

至于他们打赌结果,据说在新版的《时间简史》第6章中有说到:“事实上,从我们打赌的1975年迄今,虽然天鹅X-1号的情形并没有改变太多,但是人们已经积累了这么多对黑洞有利的观测证据,我只好认输。我进行了约定的赔偿,那就是给基普订阅一年的《藏春阁》,这使他开放的妻子相当恼火。”

不过话说回来,Hawking都那样了,他要《Private eyes》杂志来能干什么?YY吗?^_^

类似的事情还有很多,最典型的便是Kip.Thorn的老师:John.Wheeler了。他也是非常著名的物理学家,黑洞(Blackhole)这一术语便是出自他老人家。据说这个词也是含有一些流氓意味的,当时并不很能让人接受,不过现在不也成了标准词汇了:)还有一个更流氓的词汇是“黑洞无毛”^_^,这也是出自Wheeler之手,其本意不过是要说明:当一个任意形状的恒星最终坍缩成一个黑洞后,就一定是一个理想的球体,表面不会有任何起伏。不过话说回来,用这个词汇来表达,的确比较让人印象深刻。–以上关于Wheeler的内容出自《黑洞与时间弯曲》一书

真流氓总是比伪君子可爱一些,正如鲁迅先生所说:“说自己是强盗的无须防,得其反倒是好人了;说自己是好人的必须防,得其反便是强盗。”:P

自以为是

最近在研究ACE,偶然想起一件有趣的事:

几个月前刚开始研究的时候,在网上碰到有人在讨论ACE,研究过ACE的人都知道,ACE是“ADAPTIVE Communication Environment”的缩写。问题就出在这里了,那天有个家伙就很自以为是地把它写成:“Adaptive Communication Environment”,虽然只是一个大小写的不同,其实差别大了。

因为ADAPTIVE本身也是一个缩写,它的完整意思是:“A Dynamically Assembled Protocol Transformation, Integration, and eValuation Environment” ,它是ACE的前身,是D.Schmidt91年时在UCI读博时做的一个课题,Schmidt就是在做ADAPTIVE时碰到一些偶发复杂性等方面的问题,所以在92年开始了ACE项目。

其实这种自以为是的事还很多,我有个同学(就是那个“叫床”的^_^)就跟我说过两个例子:一个是“苑”字,本来他一直是读得对的,不过一回,有个人很善意地告诉他,这个字读“wan(上声)”,结果他也信以为真,此后一直读错了:)另一个更好玩,有一回他在公交车上和别人讨论PWS(Personal Web Server),结果也是旁边一个很善良的人,告诉他们那个东东叫做WPS!

不过话说回来,这种事我也干过,比如那个looser的问题,自己也寒一个。:)