Archive for February, 2006

位图操作心得

2006-2-28 21:47 | by 2ndboy

  最近要在公司项目里实现一个文档旋转的新 feature,所以有机会补上了一直以来都没怎么下过功夫的位图操作,这里把我的位图笔记摘出来一点分享一下,希望给那些想通过 SE 解决问题而不小心路过这里的朋友一些帮助:)

  先说说 DC 跟 bitmap 的关系吧,以前是从 WinAMP 视觉效果插件里面学到 memory DC 的运用的,看到人家先 CreateCompatibleDC() 再 CreateCompatibleBitmap() 就当公式记住了,从来没细想过为什么。这次在写程序的时候居然遇到了 SelectObject() “选”位图时返回 NULL 失败的现象,后来测试了一番再回头来查查《Windows 程序设计》才真正体会到了 compatible 是个什么意思。其实只有当 bitmap 跟 DC 的色深及 plane 数相同的时候 bitmap 才可以被选进 DC 里,这就叫 compatible,当然由于历史原因现在的 DC plane 就可以看作是常数 1 了。

  再来说说怎么样正确的创建彩色 bitmap,我开始的时候一直是用 CreateBitmap() 进行尝试的,结果总是会得到偏色和错位的位图,后来才在书上找到答案,CreateBitmap() 只能安全的用于创建单色位图,想通过位图数据创建彩色位图要通过其它途径。拿到彩色 bitmap 数据后要先调用 CreateBitmap() 创建一个跟 DC 兼容的位图,但最后一个参数要填 NULL,否则创建出来的位图肯定是失真的。等到用 CreateBitmap() 创建出位图后再用 SetDIBits() 给 HBITMAP 设置位图数据就可以得到彩色位图了。

  我对 GetDIBits() 和 SetDIBits() 一开始有个认识上的误解,以为位图是什么格式的就只能得到什么格式的位图数据,这样的话要针对 1、8、16、24、32 位色深分别写 5 份代码才能处理所有的位图格式,而且 16 位色又比较麻烦,后来看了 CodeProject 上的一个例子才知道,原来 GetDIBits() 和 SetDIBits() 的功能比我想象中的强大的多!你可以从 bitmap 中得到任意格式的位图数据,只要正确的设定参数和计算位图缓存的大小就可以了。这样通过 GetDIBits() 拿到 32 位色位图数据后就可以方便的实现各种特效了。

  在使用 GetDIBits() 和 SetDIBits() 的时候,BITMAPINFO 参数里面的位图高度也有点小九九,因为这里 bitmap 高度的符号决定了生成位图和获取位图数据时的扫描方向,正整数是从下往上,负整数是从上往下。如果你的程序生成的位图产生了镜像效果,那么多半调整一下这里的符号就可以恢复正常了。

  虽然由于以前大学时学 GUI 编程的时候跳过了这部分导致这次走了不少弯路,后来查书发现有些东西书上已经写得比较清楚了,不过自己尝试一下再得出结论还是要深刻和明白的多!

  后来在进行位图旋转算法优化的时候被公司里的算法牛人“教育”了一下,呵呵,收获不少,也看出了差距,别的不多说,还是要好好学习,天天向上呀!

今日杂谈

2006-2-27 21:46 | by 2ndboy

  上周从同事那里弄来几部电影,昨天看了其中的《镜面具(Mirror Mask)》,讲的是一个女孩的梦境。开始部分也没有什么特别的感觉,只是觉得构图和镜头运用都特别有某种漫画的风格,记得曾经在 FlashEmpire 看过类似风格的 flash,大概这种风格的漫画也有它自己的名字吧?!对这个没有什么研究,也就不得而知了。

  开始的印象让我觉得这似乎就是一部很普通的叙事影片,可是后面的情节就慢慢的吸引了我。说实话,我觉得这部片子是我近几年来看过的最有想象力的两部电影之一(另外一部是《千与千寻》)!主人公进入到自己的手绘漫画世界、所有人都戴面具的王国、长翅膀爱吃书还爱猜谜又最凶的猫、受到冷遇会自己跑回图书馆的书……所有这一切都是那么的新奇,只有小孩子才能想象出这么些有意思的东西来。

  这部片子的画面很美(当然,如果你不喜欢这种漫画风格的话你的感觉会是完全相反的)!而且构图都特别有梦幻的味道(我觉得这个词最恰当不过了),让人看了感觉就像是在做梦:)后面女孩被黑女巫抓到梳妆打扮时的音乐挺好听,不知道有没有得下载?一部好电影,谢谢 Diana!

—–

  最近一段时间都在老代码基础上实现一个新 feature,之前几年的工作内容都跟 GUI 不靠谱,最近一段时间的实践让我补上了大学期间被忽略掉的位图操作和 map mode,后面有时间要把它们写出来,既是分享也是备忘:)

—–

  都说杭州治安不错,对,比深圳是强多了,不过杭州也有杭州的“特色”——小偷特别多!今早在公交车上让我遇到两拨三个,其中一个西装革履像极了上班族,只是既不打盹儿、又不看电视还没看着窗外——眼睛都在大家的包和口袋上,另外两个后来的都是中学生打扮,一路上跟贼坐邻座搞得我反胃无比,好在这些“人”都没有得手。

  Team 的美工刚在上周四被偷了手机,今天看到这些人渣不禁让我感叹:天下何时无贼?

给 greenTown 个交代,自己也学习一下

2006-2-14 08:13 | by 2ndboy

  答应你有时间研究一下那篇文章上的程序的,不过过节加上工作、杂事就耽误了这么久,真是过意不去:)

  其实我自己用 WTL 的时候也没有深究过其实现,这也是个学习的好机会:)文章里的程序如下:

  1. template<class T>
  2.  class B1
  3.  {
  4.  public:
  5.    void SayHi()
  6.    {
  7.      T *pT = static_cast<t *>( this )// 技巧
  8.      pT->PrintClassName();
  9.    }
  10.  protected:
  11.    void PrintClassName() { cout << "This is B1\n"; }
  12.  };
  13.  class D1 : public B1<d1>
  14.  {};
  15.  class D2 : public B1<d2>
  16.  {
  17.  protected:
  18.    void PrintClassName() { cout << "This is D2\n"; }
  19.  };
  20.  int main( void )
  21.  {
  22.    D1 d1;
  23.    D2 d2;
  24.    d1.SayHi()// This is B1
  25.    d2.SayHi()// This is D2
  26.    return( 0 );
  27.  }

  VC6 下编译会报“error C2248: ‘PrintClassName’ : cannot access protected member declared in class ‘D2’”的错误,你的疑问就在这里。我用有限的 template 知识分析了一下觉得这是个笔误:)我觉得 D2 中对 PrintClassName() 的声明一定要是 public 的。

  其实上面的例子更接近下面这个(我叫它例 1):

  1. class D2
  2.  {
  3.  protected:
  4.    void PrintClassName() { cout << "This is D2\n"; }
  5.  };
  6.  class B1
  7.  {
  8.  public:
  9.    void SayHi( D2 *pD2 ) { pD2->PrintClassName(); }  // error
  10.  };
  11.  int main( void )
  12.  {
  13.    D2 d2;
  14.    B1 b1;
  15.    b1.SayHi( &d2 );
  16.    return( 0 );
  17.  }

  而不是这个(我叫它例 2):

  1. class D2
  2.  {
  3.  protected:
  4.    void PrintClassName() { cout << "This is D2\n"; }
  5.  };
  6.  class B1 : public D2
  7.  {
  8.  public:
  9.    void SayHi() { PrintClassName(); }  // OK
  10.  };
  11.  int main( void )
  12.  {
  13.    B1 b1;
  14.    b1.SayHi();
  15.    return( 0 );
  16.  }

  模板类 B1 只是使用 D2 进行了实例化,并非继承自 D2(正好相反是 D2 继承自 B1)。所以对 B1 来说,D2 对于它更像是例 1 里面的情况(其实把例 1 里面的 B1 写成模板类更贴切一点,但是现在这样更好理解:))。

  如果是 B1 和 D2 的关系像例 2 里面那样,是 B1 继承自 D2(公有继承),派生类访问基类保护成员函数当然就是没问题的!但是文章里的程序更像是例 1 里的情况,B1 仅仅知道 D2 而并非从 D2 派生而来,要直接访问 D2 中的受保护成员函数当然是不可以的!

  当然 D2::PrintClassName() 确实是覆盖了基类的同名成员函数,但从实践来看编译器是优先对模板实例化进行了解析而非继承关系(在我的印象中,C++98 中对模板的规范好像还不够细致)。为了验证我的想法而排除编译器的因素,我又用 MinGW(相当于 Win32 平台下的 gcc)编译了文章中的例子,结果仍然是错误的,而出错原因跟在 VC 里面是一样的。当然,这个测试还不全面,有时间我再多用几款编译器试试看。

  呵呵,上面就是我的理解和解释,不知是否有够说服力?我的模板知识确实有限,而且只测了两款编译器,欢迎有不同意见和想法的高人指点:)

  弄这个东西的过程中我想起了刚学 C++ 时一个搞晕我的地方:

  1. class A
  2.  {
  3.  public:
  4.    void SayHi( A *pA ) { pA->PrintClassName(); }
  5.  private:
  6.    void PrintClassName() { cout << "This is A\n"; }
  7.  };
  8.  int main( void )
  9.  {
  10.    A a1;
  11.    A a2;
  12.    a2.SayHi( &a1 );
  13.    return( 0 );
  14.  }

我刚学 C++ 的时候看到这个正确的例程就是想不通为什么 A::SayHi() 可以直接用 -> 操作符访问到其它对象的私有成员函数:)后来看到 void SayHi( A *pA ) { pA->A::PrintClassName(); } 这种写法马上就想通了:)

  WTL 里面对模板类还有一种形式被大量运用(尤其是对控件的封装):

  1. template <class TBase>
  2.  class CButtonT : public TBase
  3.  {
  4.    ... ...
  5.  }

typedef CButtonT CButton;
这里面就没有什么技巧可言了,要用按钮控件的时候就直接用 CButton,我觉得除了基类可代换之外跟 class CButton : public CWindow 没有任何区别,但是也不会有人想要去重写一个有 N 多 wrap 成员函数的 CWindow 吧?!

集成 gzip,闲话库

2006-2-8 08:11 | by 2ndboy

  以前遇到 xxx.tar.gz 这样的文件都是直接用 tar -xzf xxx.tar.gz 释放了,还真没想过为什么人们都不直接用 gzip 压缩一堆文件,而要先用 tar 打包再用 gzip 压缩。今天由于要在一个程序里面集成 gzip 压缩功能而搞清楚了其中奥秘。呵呵,很简单,事实就是——gzip 根本就不支持多文件压缩:-)

  gzip 的核心函数 zip() 的原型是:int zip( int in, int out );,一个要进行压缩的输入文件描述符和一个存放压缩数据的输出文件描述符。你只能用 CRT 函数 open() 打开两个文件交给它处理,所以想把多个文件压缩到一个包里面是不可能的,如果你先后给它两个输入文件进行压缩会导致两个文件被连在一起。

  说到向程序中集成 gzip 还真的遇到点小麻烦,经过一番研究后得出如下方法,包含 gzip 头文件时要这样:

  1. extern "C"
  2.  {
  3.    #include <tailor.h/* 一定要放在 gzip.h 前面 */
  4.    #include <gzip.h>
  5.  }

  tailor.h 里面根据不同的 OS 常量对代码进行了一些配置(定义了一些相应的常量),所以要放在 gzip.h 前面。而由于 gzip 古老的代码里使用了老旧的形参声明方式,所以要用 extern “C” 把头文件包起来,免得 VC6 这样的编译器犯病。我所谓的老旧形参声明方式是指:

  1. int zip( in, out )
  2.   int in, out;
  3. {
  4.   ... ...
  5. }

  C/C++ 程序员不像 Java 和新生代动态语言程序员那么幸福,语言本身就自带了很多丰富多彩的官方扩展库,C/C++ 程序员用的库大都需要自己平时的积累。比如 PHP 带的 XML 解析库,C/C++ 程序员想在自己的程序里解析 XML 就要去自己集成大如 libxml2 或者小如 TinyXML 这样的第三方库,但前提是你要听说过它们,而且了解它们支持和不支持的特性,优缺点等等,这都是积累!如果有一天你要在自己的程序里增加像 Perl 那样的正则表达式支持,你会突然做梦就梦到要去找 pcre 吗?这需要平时的积累。

  随便举几个我用过的开源库的例子吧,大家有好用又经典的库,欢迎补充:
解析简单的 XML(比如配置文件):TinyXML
字符编码转换:iconv
MP3 解码(支持 VBR):libmad
处理 PNG 格式的图片:libpng
跨平台的音频回放:PortAudio
数据无损压缩:zlib/gzip
HTTP/FTP 客户端实现:libcurl
嵌入式文件数据库(支持 SQL):SQLite

  由于跨平台特性和可以拿到源码的原因,我格外青睐网上的开源库,即便是 Windows 提供了相同功能的 native 库:-)随着 C++0x 的逐渐成形,boost 的很多内容都会进入 C++ 标准,C++ 程序员缺乏好库的局面会有所改善了,希望 x 早日定格吧:-)

我的南方春节

2006-2-5 08:13 | by 2ndboy

  由于再过三个月老哥结婚我就得回家去,所以为了避免折腾就决定在 GF 家过年了。这是我长这么大在南方过的第一个春节,所以对比了两地(只是两地,其实南北方的差异远不止我这里说的这么多)风俗,发现了很多不同和有意思的地方。

  最大的不同应该是放鞭炮的时间了!在我们家那里,放鞭炮都统一在除夕的半夜 12 点,听老妈说过是为了接神,不过是为了接应哪路神仙还没有考证过,反正一到时间,全城鞭炮齐鸣,整个天空都被映成红色的了。GF 家这边鞭炮在晚饭前就放的差不多了,所以响的最凶的似乎是接近傍晚时分。而且 GF 家这边也没有点旺火的习惯,反正我记得小时候跟伙伴们出去玩鞭炮的时候一整条街上每家门口都点着旺火,有些用煤堆起来的旺火可以整晚都着着,供我们玩冷了围拢过去烤火。当然,你得小心有人恶作剧丢进去的鞭炮:-)

  第二个不同当然是吃的东西了,北方过年是肯定要吃饺子的,饺子就是“交子”,把旧的子时交出去,迎接新的一年。南方相对应的食物应该是年糕,象征一年更比一年高:-)但晚餐的主食还是米饭。鉴于我今年过年没有吃上饺子,所以回杭州马上就去超市买了饺子回来煮了吃,哈哈,算是弥补了自己心理上的过年“缺憾”^_^

  其它的不同还在于,南方人似乎没有除夕守夜的习惯,不过这也难怪,因为没有暖气的原因,人们在冬天晚上更愿意早早呆在暖暖和和的被窝里。给压岁钱的习惯也有所不同,在北方,我们给压岁钱都是直接给钱的,而 GF 家这里压岁钱是必定要装在红包里交到小孩子手上的,更加正式。

  今年我还有幸见到了豆腐的制作过程,把用水泡过的黄豆送到加工厂里面磨碎,然后拿回家里用纱布包起来滤除粗纤维,将过滤后的东西煮熟,用盐卤点豆腐的过程我没有看到,不过似乎应该发生在煮的过程中吧?!这时应该就得到了豆腐脑,然后仍然用纱布把豆腐脑包起来,上面放上重物,第二天,我们就得到了味道很好的老豆腐。这种自制豆腐光光是蘸着酱油吃就已经非常好吃了,可惜的是杭州的超市和菜场里都买不到:-(

  大年初一早上天降大雾,能见度大概只有四、五米,按照习惯,GF 家这边的人会在这天早上给祖先上坟,由于我从小长大的地方没有家族的祖坟(在老家),所以我们北方是否也有这个习惯我就不得而知了。

  南北方的馄饨也很不一样,我们家的馄饨馅儿很少,制作方法也很简单,其实就是一个面片对折,里面包一点点馅儿。而南方的馄饨馅儿非常多,制作方法也略显复杂,包好以后呈环形,有点像古人出门时背的包袱。其实依我看这里的大馄饨就相当于北方的饺子,个儿大馅儿多!

  大年初一雾散后是个好天气,我跟着 GF 一家出去溜达,回来时到了村边的小庙,庙门上挂着他们祖先书于“民国壬午冬月”的“独山寺”三个字,对天干地支纪年法不熟,所以实在推算不出这民国壬午是公元哪一年,但肯定是超过八十年的历史了吧?!庙的附近有棵大樟树,听 GF 的老爸说已经有数百年的历史了!看着这大树不禁生出些感慨,不过就是不会吟诗哈哈^_^

  过年这几天闲来无事放松放松,聚起几个亲戚一起打牌搓麻将,我扑克和麻将平时都玩的很少,尤其是麻将一年最多玩两次,但奇怪的是打牌我手臭的很,但麻将不管是北方规则还是南方规则我每次一打最后总是台面上的赢家。难道我打麻将居然还有点天赋,哈哈:-)

  心野了 7、8 天,一想到明天就要正式上班了,竟有点不舍:-)。不过再过三个月就是五一了,到时老哥结婚就可以跟一年没见的家人团聚了。新年新气象,祝家人和自己还有路过这里的朋友狗年旺旺,诸事顺利,身体健康!!!