第4章图象的半影调和抖动技术 在介绍本章内容之前,先提出一个问题?普通的黑白针式打印机能打出灰度图来吗?如果说 能,从针式打印机的打印原理来分析,似乎是不可能的。因为针打是靠撞针击打色带在纸上 形成黑点的,不可能打出灰色的点来:如果说不能,可是我们的确见过用针式打印机打印出 来的灰色图象。到底是怎么回事呢? 你再仔细看看那些打印出来的所谓的灰色图象,最好用放大镜看。你会发现,原来这些灰色 图象都是由一些黑点组成的,黑点多一些,图象就暗一些;黑点少一些,图案就亮一些。下 面这几张图就很能说明这一点。 图41用黑白两种颜色打印出灰度效果 图41中最左边的是原图,是一幅真正的灰度图,另外三张图都是黑白二值图。容易看出, 最左的那幅和原图最接近 由二值图象显示出灰度效果的方法,就是我们今天要讲的半影调 Halftone)技术,它的一个主 要用途就是在只有二值输出的打印机上打印图象。我们介绍两种方法:图案法和抖动法。 41图案法 图案法( patterning)是指灰度可以用一定比例的黑白点组成的区域表示,从而达到整体图象的 灰度感。黑白点的位置选择称为图案化 在具体介绍图案法之前,先介绍一下分辨率的概念。计算机显示器,打印机,扫描仪等设备 的一个重要指标就是分辨率,单位是dpi( dot per inch),即每英寸点数,点数越多,分辨率就 越高,图象就越清晰。让我们来计算一下,计算机显示器的分辨率有多高。设显示器为15 英寸(指对角线长度),最多显示1280×1024个点。因为宽高比为4:3,所以宽有12英寸, 高有9英寸,则该显示器的水平分辨率为106dpi,垂直分辨率为1138dpi。一般的激光打印 机的分辨率有300dpi×30odpi,600dpi×600dpi,720dpi×720dpi。所以打出来的图象要比计 算机显示出来的清晰的多。扫描仪的分辨率要高一些,数码相机的分辨率更高。 言归正传,前面讲了,图案化使用图案来表示象素的灰度,那么我们来做一道计算题。假设 有一幅240×180×8bit的灰度图,当用分辨率为300dpi×300dpi的激光打印机将其打印到 128×9.6英寸的纸上时,每个象素的图案有多大? 这道题很简单,这张纸最多可以打(300×12.8)×(300×96=3840×2880个点,所以每个象 素可以用(3840/240)×(2880180)=16×16个点大小的图案来表示,即一个象素256个点。如 果这16×16的方块中一个黑点也没有,就可以表示灰度256:有一个黑点,就表示灰度255: 依次类推,当都是黑点时,表示灰度0。这样,16×16的方块可以表示257级灰度,比要求 的8b共256级灰度还多了一个。所以上面的那幅图的灰度级别完全能够打印出来
第 4 章 图象的半影调和抖动技术 在介绍本章内容之前,先提出一个问题?普通的黑白针式打印机能打出灰度图来吗?如果说 能,从针式打印机的打印原理来分析,似乎是不可能的。因为针打是靠撞针击打色带在纸上 形成黑点的,不可能打出灰色的点来;如果说不能,可是我们的确见过用针式打印机打印出 来的灰色图象。到底是怎么回事呢? 你再仔细看看那些打印出来的所谓的灰色图象,最好用放大镜看。你会发现,原来这些灰色 图象都是由一些黑点组成的,黑点多一些,图象就暗一些;黑点少一些,图案就亮一些。下 面这几张图就很能说明这一点。 图 4.1 用黑白两种颜色打印出灰度效果 图 4.1 中最左边的是原图,是一幅真正的灰度图,另外三张图都是黑白二值图。容易看出, 最左的那幅和原图最接近。 由二值图象显示出灰度效果的方法,就是我们今天要讲的半影调(halftone)技术,它的一个主 要用途就是在只有二值输出的打印机上打印图象。我们介绍两种方法:图案法和抖动法。 4.1 图案法 图案法(patterning)是指灰度可以用一定比例的黑白点组成的区域表示,从而达到整体图象的 灰度感。黑白点的位置选择称为图案化。 在具体介绍图案法之前,先介绍一下分辨率的概念。计算机显示器,打印机,扫描仪等设备 的一个重要指标就是分辨率,单位是 dpi(dot per inch),即每英寸点数,点数越多,分辨率就 越高,图象就越清晰。让我们来计算一下,计算机显示器的分辨率有多高。设显示器为 15 英寸(指对角线长度),最多显示 1280×1024 个点。因为宽高比为 4:3,所以宽有 12 英寸, 高有 9 英寸,则该显示器的水平分辨率为 106dpi,垂直分辨率为 113.8dpi。一般的激光打印 机的分辨率有 300dpi×300dpi,600dpi×600dpi,720dpi×720dpi。所以打出来的图象要比计 算机显示出来的清晰的多。扫描仪的分辨率要高一些,数码相机的分辨率更高。 言归正传,前面讲了,图案化使用图案来表示象素的灰度,那么我们来做一道计算题。假设 有一幅 240×180×8bit 的灰度图,当用分辨率为 300dpi×300dpi 的激光打印机将其打印到 12.8×9.6 英寸的纸上时,每个象素的图案有多大? 这道题很简单,这张纸最多可以打(300×12.8) ×(300×9.6)=3840×2880 个点,所以每个象 素可以用(3840/240)×(2880/180)=16×16 个点大小的图案来表示,即一个象素 256 个点。如 果这 16×16 的方块中一个黑点也没有,就可以表示灰度 256;有一个黑点,就表示灰度 255; 依次类推,当都是黑点时,表示灰度 0。这样,16×16 的方块可以表示 257 级灰度,比要求 的 8bit 共 256 级灰度还多了一个。所以上面的那幅图的灰度级别完全能够打印出来
这里有一个图案构成的问题,即黑点打在哪里?比如说,只有一个黑点时,我们可以打在正 中央,也可以打16×16的左上角。图案可以是规则的,也可以是不规则的。一般情况下, 有规则的图案比随即图案能够避免点的从集,但有时会导致图象中有明显的线条 如图41中,2×2的图案可以表示5级灰度,当图象中有一片灰度为的1的区域时,如图 42所示,有明显的水平和垂直线条。 图422×2的图案 图43规则图案导致线条 如果想存储256级灰度的图案,就需要256×16×16的二值点阵,占用的空间还是相当可观 的。有一个更好的办法是:只存储一个整数矩阵,称为标准图案,其中的每个值从0到255 图象的实际灰度和阵列中的每个值比较,当该值大于等于灰度时,对应点打一黑点。下面举 个25级灰度的例子加以说明 0142258 62416 10154n17 图44标准图案举例 图44中,左边为标准图案,右边为灰度为5的图案,共有10个黑点,15个白点。其实道 理很简单,灰度为0时全是黑点,灰度每增加1,减少一个黑点。要注意的是,5×5的图案 可以表示26种灰度,当灰度是25才是全白点,而不是灰度为24时。 下面介绍一种设计标准图案的算法,是由Limb在1969年提出的。 先以一个2×2的矩阵开始:设M1= 通过递归关系有Mn+1= 4M、+2U 4M+x4M+乙 以x],其中M和Un均为2×2n的方阵,Uh的所有元素都是1 08210 24146 311 根据这个算法,可以得到M2=157135 ,为16级灰度的标准图案 M3(8×8阵)比较特殊,称为 Bayer抖动表。M是一个16×16的矩阵 根据上面的算法,如果利用M3一个象素要用8×8的图案表示,则一幅N×N的图将变成 8N×8N大小。如果利用M4,就更不得了,变成16N×16N了。能不能在保持原图大小的
这里有一个图案构成的问题,即黑点打在哪里?比如说,只有一个黑点时,我们可以打在正 中央,也可以打 16×16 的左上角。图案可以是规则的,也可以是不规则的。一般情况下, 有规则的图案比随即图案能够避免点的丛集,但有时会导致图象中有明显的线条。 如图 4.1 中,2×2 的图案可以表示 5 级灰度,当图象中有一片灰度为的 1 的区域时,如图 4.2 所示,有明显的水平和垂直线条。 图 4.2 2×2 的图案 图 4.3 规则图案导致线条 如果想存储 256 级灰度的图案,就需要 256×16×16 的二值点阵,占用的空间还是相当可观 的。有一个更好的办法是:只存储一个整数矩阵,称为标准图案,其中的每个值从 0 到 255。 图象的实际灰度和阵列中的每个值比较,当该值大于等于灰度时,对应点打一黑点。下面举 一个 25 级灰度的例子加以说明。 图 4.4 标准图案举例 图 4.4 中,左边为标准图案,右边为灰度为 15 的图案,共有 10 个黑点,15 个白点。其实道 理很简单,灰度为 0 时全是黑点,灰度每增加 1,减少一个黑点。要注意的是,5×5 的图案 可以表示 26 种灰度,当灰度是 25 才是全白点,而不是灰度为 24 时。 下面介绍一种设计标准图案的算法,是由 Limb 在 1969 年提出的。 先以一个 2 × 2 的矩阵开始:设 M1= ,通过递归关系有 Mn+1= ,其中 Mn 和 Un 均为 2 n×2 n 的方阵,Un 的所有元素都是 1。 根据这个算法,可以得到 M2= ,为 16 级灰度的标准图案。 M3(8×8 阵)比较特殊,称为 Bayer 抖动表。M4 是一个 16×16 的矩阵。 根据上面的算法,如果利用 M3 一个象素要用 8×8 的图案表示,则一幅 N×N 的图将变成 8N×8N 大小。如果利用 M4,就更不得了,变成 16N×16N 了。能不能在保持原图大小的
情况下利用图案化技术呢?一种很自然的想法是:如果用M2阵,则将原图中每8×8个点 中取一点,即重新采样,然后再应用图案化技术,就能够保持原图大小。实际上,这种方法 并不可行。首先,你不知道这8×8个点中找哪一点比较合适,另外,8×8的间隔实在太大 了,生成的图象和原图肯定相差很大,就象图4.1最右边的那幅图一样 我们可以采用这样的做法:假设原图是256级灰度,利用 Bayer抖动表,做如下处理 f(gyx]>>2)> bayer&7[x&7then打一白点else打一黑点 其中,xy代表原图的象素坐标,gyx代表该点灰度。首先将灰度右移两位,变成64级, 然后将x,y做模8运算,找到 Bayer表中的对应点,两者做比较,根据上面给出的判据做 处理 我们可以看到,模8运算使得原图分成了一个个8×8的小块,每个小块和8×8的 Bayer 表相对应。小块中的每个点都参与了比较,这样就避免了上面提到的选点和块划分过大的问 题。模8运算实质上是引入了随机成分,这就是我们下面要讲到的抖动技术 图45就是利用了这个算法,使用M( Bayer抖动表)阵得到的;图6是使用M阵得到的 可见两者的差别并不是很大,所以一般用 Bayer表就可以了 图45利用M3抖动生成的图 图46利用M4抖动生成的图
情况下利用图案化技术呢?一种很自然的想法是:如果用 M2 阵,则将原图中每 8×8 个点 中取一点,即重新采样,然后再应用图案化技术,就能够保持原图大小。实际上,这种方法 并不可行。首先,你不知道这 8×8 个点中找哪一点比较合适,另外,8×8 的间隔实在太大 了,生成的图象和原图肯定相差很大,就象图 4.1 最右边的那幅图一样。 我们可以采用这样的做法:假设原图是 256 级灰度,利用 Bayer 抖动表,做如下处理 if (g[y][x]>>2) > bayer[y&7][x&7] then 打一白点 else 打一黑点 其中,x,y 代表原图的象素坐标,g[y][x]代表该点灰度。首先将灰度右移两位,变成 64 级, 然后将 x,y 做模 8 运算,找到 Bayer 表中的对应点,两者做比较,根据上面给出的判据做 处理。 我们可以看到,模 8 运算使得原图分成了一个个 8×8 的小块,每个小块和 8×8 的 Bayer 表相对应。小块中的每个点都参与了比较,这样就避免了上面提到的选点和块划分过大的问 题。模 8 运算实质上是引入了随机成分,这就是我们下面要讲到的抖动技术。 图 4.5 就是利用了这个算法,使用 M3(Bayer 抖动表)阵得到的;图 6 是使用 M4 阵得到的, 可见两者的差别并不是很大,所以一般用 Bayer 表就可以了。 图 4.5 利用 M3 抖动生成的图 图 4.6 利用 M4 抖动生成的图
下面是算法的源程序,是针对 Bayer表的。因为它是个常用的表,我们不再利用Lmb公式, 而是直接给出。针对M阵的算法是类似的,不同的地方在于,要用Limb公式得到M阵, 灰度也不用右移2位。要注意的是,为了处理的方便,我们的结果图仍采用256级灰度图, 不过只用到了0和255两种灰度 BYTE BayerPatternl8[8j={0,32,8,402,34,10,42, 48,16,56,24,50,18,58,26, 12,44,4,36,1446,6,38, 60,28,52,20,62,30,5422 3,35,11,43,1,33,941 1,19,59,27,49,17,57,25, 15,47,7,39,13,45,5,37, BOOL LimbPattern M3(HWND hwnd) DWORD OffBits Bufsize LPBITMAPINFOHEADER Iplmg Data; LPSTR HLOCAL hTemplmgData LPBITMAPINFOHEADER Ip Templmg Data LPSTR lpTempPtr HDC HFILE LONG num OffBits-bf. bfOffBits-sizeof(BITMAPFILEHEADER) Bufsize= OffBits+bi. biHeight*Line Bytes,/要开的缓冲区大小 if(hTemplmg DataLocalAlloc(LHND, BufSize))==NULL) Message Box(hWnd, "Error alloc memory!","Error Message", MB OK MB ICONEXCLAMATION) return False. Iplmg Data=(LPBITMAPINFOHEADER)GlobalLock(hlmg Data); IpTemplmg Data(LPBITMAPINFOHEADER)LocalLock(hTemplmg Data); ∥拷贝头信息和位图数据 memcpy(lpTemplmg Data, Iplmg Data, BufSize) for(y=0; y<bi. biHeight; y++)i pPr为指向原图位图数据的指针
下面是算法的源程序,是针对 Bayer 表的。因为它是个常用的表,我们不再利用 Limb 公式, 而是直接给出。针对 M4 阵的算法是类似的,不同的地方在于,要用 Limb 公式得到 M4 阵, 灰度也不用右移 2 位。要注意的是,为了处理的方便,我们的结果图仍采用 256 级灰度图, 不过只用到了 0 和 255 两种灰度。 BYTE BayerPattern[8][8]={ 0,32,8,40,2,34,10,42, 48,16,56,24,50,18,58,26, 12,44,4,36,14,46,6,38, 60,28,52,20,62,30,54,22, 3,35,11,43,1,33,9,41, 51,19,59,27,49,17,57,25, 15,47,7,39,13,45,5,37, 63,31,55,23,61,29,53,21}; BOOL LimbPatternM3(HWND hWnd) { DWORD OffBits,BufSize LPBITMAPINFOHEADER lpImgData; LPSTR lpPtr; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpTempPtr; HDC hDc; HFILE hf; LONG x,y; unsigned char num; OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); BufSize=OffBits+bi.biHeight*LineBytes;//要开的缓冲区大小 if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL) { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); //拷贝头信息和位图数据 memcpy(lpTempImgData,lpImgData,BufSize); for(y=0;y<bi.biHeight;y++){ //lpPtr 为指向原图位图数据的指针
IpPtr=(char *)Iplmg Data( BufSize-LineBytes-y * Line Bytes) pTempPtr为指向新图位图数据的指针 lpTempPtr=(char *)Ip Templmg Data+ ( BufSize-LineBytes-y*Line Bytes) for(x=0 X2)> BayerPattern[y&x&刀)∥右移两位后做比较 ( pTempPtr++)=( unsigned char)255,/白点 else*( IpTempPtr++)=( unsigned char)O,∥打黑点 if(hBitmap l=NULL) DeleteObject(hBitmap) hDc=GetDC(hWnd) 形成新的位图 hBitmap=CreateDI Bitmap(hDc, (LPBITMAPINFOHEADER)Ip Templmg Data, (LONG)CBM INIT. sizeof( BITMAPINFOHEADER)+ umColors*sizeof(RGBQUAD) (LPBITMAPINFO)IpTemplmg Data, DIB RGB COLORS) hf- creat("c: Wimbm3 bmp", 0); Iwrite(hf, (LPSTR)&bf, sizeof( BI TMAPFILEHEADER): Iwrite(hf, (LPSTR)Ip Templmg Data, BufSize) Iclose(hf); ∥释放内存和资源 ReleaseDC(hWnd, hDc) LocalUnlock(hTemplmg Data Local Free(hTemplmg Data) GlobalUnlock(hmg Data) return TRUE. 42抖动法 让我们考虑更坏的情况:即使使用了图案化技术,仍然得不到要求的灰度级别。举例说明: 假设有一幅600×450×8bt的灰度图,当用分辨率为30dpi×30odpi的激光打印机将其打 印到8×6英寸的纸上时,每个象素可以用(2400600×(1800/450=4×4个点大小的图案来
lpPtr=(char *)lpImgData+(BufSize-LineBytes-y*LineBytes); //lpTempPtr 为指向新图位图数据的指针 lpTempPtr=(char *)lpTempImgData+(BufSize-LineBytes-y*LineBytes); for(x=0;x>2) > BayerPattern[y&7][x&7]) //右移两位后做比较 *(lpTempPtr++)=(unsigned char)255; //打白点 else *(lpTempPtr++)=(unsigned char)0; //打黑点 } } if(hBitmap!=NULL) DeleteObject(hBitmap); hDc=GetDC(hWnd); //形成新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); hf=_lcreat("c:\\limbm3.bmp",0); _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER)); _lwrite(hf,(LPSTR)lpTempImgData,BufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); LocalUnlock(hTempImgData); LocalFree(hTempImgData); GlobalUnlock(hImgData); return TRUE; } 4.2 抖动法 让我们考虑更坏的情况:即使使用了图案化技术,仍然得不到要求的灰度级别。举例说明: 假设有一幅 600×450×8bit 的灰度图,当用分辨率为 300dpi×300dpi 的激光打印机将其打 印到 8×6 英寸的纸上时,每个象素可以用(2400/600)×(1800/450)=4×4 个点大小的图案来
表示,最多能表示17级灰度,无法满足256级灰度的要求。可有两种解决方案:(1)减小图 象尺寸,由600×450变为150×113:(2)降低图象灰度级,由256级变成16级。这两种方 案都不理想。这时,我们就可以采用“抖动法”( dithering)的技术来解决这个问题。其实刚 才给出的算法就是一种抖动算法,称为规则抖动( (regular dithering)。规则抖动的优点是算法 简单:缺点是图案化有时很明显,这是因为取模运算虽然引入了随机成分,但还是有规律的。 另外,点之间进行比较时,只要比标准图案上点的值大就打白点,这种做法并不理想,因为 如果当标准图案点的灰度值本身就很小,而图象中点的灰度只比它大一点儿时,图象中的点 更接近黑色,而不是白色。一种更好的方法是将这个误差传播到邻近的象素 下面介绍的 Floyd- Steinberg算法就采用了这种方案。 假设灰度级别的范围从b( black)到w( white),中间值t为(b+w)/2,对应256级灰度, b=0,w=25t=1275。设原图中象素的灰度为g,误差值为e,则新图中对应象素的值用如下 的方法得到: if g >t the 打白点 打黑点 3/8×e加到右边的象素 3/8×e加到下边的象素 1/4×e加到右下方的象素 算法的意思很明白:以256级灰度为例,假设一个点的灰度为130,在灰度图中应该是一个 灰点。由于一般图象中灰度是连续变化的,相邻象素的灰度值很可能与本象素非常接近,所 以该点及周围应该是一片灰色区域。在新图中,130大于128,所以打了白点,但130离真 正的白点255还差的比较远,误差e=130-255=-125比较大。,将3/8×(-125)加到相邻象素 后,使得相邻象素的值接近0而打黑点。下一次,e又变成正的,使得相邻象素的相邻象素 打白点,这样一白一黑一白,表现出来刚好就是灰色。如果不传递误差,就是一片白色了。 再举个例子,如果一个点的灰度为250,在灰度图中应该是一个白点,该点及周围应该是一 片白色区域。在新图中,虽然e=5也是负的,但其值很小,对相邻象素的影响不大,所以 还是能够打出一片白色区域来。这样就验证了算法的正确性。其它的情况你可以自己推敲。 图47是利用 Floyd- Steinberg算法抖动生成的图
表示,最多能表示 17 级灰度,无法满足 256 级灰度的要求。可有两种解决方案:(1)减小图 象尺寸,由 600×450 变为 150×113;(2)降低图象灰度级,由 256 级变成 16 级。这两种方 案都不理想。这时,我们就可以采用“抖动法”(dithering)的技术来解决这个问题。其实刚 才给出的算法就是一种抖动算法,称为规则抖动(regular dithering)。规则抖动的优点是算法 简单;缺点是图案化有时很明显,这是因为取模运算虽然引入了随机成分,但还是有规律的。 另外,点之间进行比较时,只要比标准图案上点的值大就打白点,这种做法并不理想,因为, 如果当标准图案点的灰度值本身就很小,而图象中点的灰度只比它大一点儿时,图象中的点 更接近黑色,而不是白色。一种更好的方法是将这个误差传播到邻近的象素。 下面介绍的 Floyd-Steinberg 算法就采用了这种方案。 假设灰度级别的范围从 b(black)到 w(white),中间值 t 为(b+w)/2,对应 256 级灰度, b=0,w=255,t=127.5。设原图中象素的灰度为 g,误差值为 e,则新图中对应象素的值用如下 的方法得到: if g > t then 打白点 e=g-w else 打黑点 e=g-b 3/8 × e 加到右边的象素 3/8 × e 加到下边的象素 1/4 × e 加到右下方的象素 算法的意思很明白:以 256 级灰度为例,假设一个点的灰度为 130,在灰度图中应该是一个 灰点。由于一般图象中灰度是连续变化的,相邻象素的灰度值很可能与本象素非常接近,所 以该点及周围应该是一片灰色区域。在新图中,130 大于 128,所以打了白点,但 130 离真 正的白点 255 还差的比较远,误差 e=130-255=-125 比较大。,将 3/8×(-125)加到相邻象素 后,使得相邻象素的值接近 0 而打黑点。下一次,e 又变成正的,使得相邻象素的相邻象素 打白点,这样一白一黑一白,表现出来刚好就是灰色。如果不传递误差,就是一片白色了。 再举个例子,如果一个点的灰度为 250,在灰度图中应该是一个白点,该点及周围应该是一 片白色区域。在新图中,虽然 e=-5 也是负的,但其值很小,对相邻象素的影响不大,所以 还是能够打出一片白色区域来。这样就验证了算法的正确性。其它的情况你可以自己推敲。 图 4.7 是利用 Floyd-Steinberg 算法抖动生成的图
图47利用 Floyd-Steinberg算法抖动生成的图 下面我们给出 Floyd- Steinberg算法的源代码。有一点要说明,我们原来介绍的程序都是先开 一个char类型的缓冲区,用来存储新图数据,但在这个算法中,因为e有可能是负数,为 了防止得到的值超岀char能表示的范围,我们使用了一个int类型的缓冲区存储新值。另外, 当按从左到右,从上到下的顺序处理象素时,处理过的象素以后不会再用到了,所以用这个 int类型的缓冲区存储新值是可行的。全部象素处理完后,再将这些值拷贝到char类型的缓 冲区去。 BOOL Steinberg(HWND hWnd) OffBits. BufSize Int BufSize LPBITMAPINFOHEADER lplmg Data HLOCAL hTemplmg Data LPBITMAPINFOHEADER lp Templmg Data LPSTR IpPt LPSTR Ip TempPtr HDC HFILE LONG HLOCAL hInt: *lplnt Buf, *lpIntPtr; OffBits为 BITMAPINFOHEADER结构长度加调色板的大小 OffBitsbf. bfoffBits-sizeof( BITMAPFILEHEADER) Bufsize= OffBits+bi. biHeight*Line Bytes,要开的缓冲区的大小 if((hTemplmg DataLocalAlloc(LHND, BufSize))==NULL)
图 4.7 利用 Floyd-Steinberg 算法抖动生成的图 下面我们给出 Floyd-Steinberg 算法的源代码。有一点要说明,我们原来介绍的程序都是先开 一个 char 类型的缓冲区,用来存储新图数据,但在这个算法中,因为 e 有可能是负数,为 了防止得到的值超出 char 能表示的范围,我们使用了一个 int 类型的缓冲区存储新值。另外, 当按从左到右,从上到下的顺序处理象素时,处理过的象素以后不会再用到了,所以用这个 int 类型的缓冲区存储新值是可行的。全部象素处理完后,再将这些值拷贝到 char 类型的缓 冲区去。 BOOL Steinberg(HWND hWnd) { DWORD OffBits,BufSize,IntBufSize; LPBITMAPINFOHEADER lpImgData; HLOCAL hTempImgData; LPBITMAPINFOHEADER lpTempImgData; LPSTR lpPtr; LPSTR lpTempPtr; HDC hDc; HFILE hf; LONG x,y; unsigned char num; float e,f; HLOCAL hIntBuf; int *lpIntBuf,*lpIntPtr; int tempnum; //OffBits 为 BITMAPINFOHEADER 结构长度加调色板的大小 OffBits=bf.bfOffBits-sizeof(BITMAPFILEHEADER); BufSize=OffBits+bi.biHeight*LineBytes;//要开的缓冲区的大小 if((hTempImgData=LocalAlloc(LHND,BufSize))==NULL)
Message Box(hWnd, "Error alloc memory! ", "Error Message", MB OK MB ICONEXCLAMATION) return False Int BufSize=(DWORD)bi. biHeight*Line Bytes*sizeof(int) if( hInt Buf= LocalAlloc( LHND IntBufsize)= NULL)//int类型的缓冲区 Message Box(hWnd, "Error alloc memory!","Error Message", MB OK MB ICONEXCLAMATION LocalFree(hTemplmg Data) eturn false. oImg Data( LPBITMAPINFOHEADER)GlobalLock(hlmg Data) IpTemplmg Data=(LPBITMAPINFOHEADER)LocalLock(hTemplmg Data lpInt Buf(int*)LocalLock(hInt Buf); ∥拷贝头信息 memcpy(lp Templmg Data, Iplmg Data, OffBits) ∥将图象数据拷贝到int类型的缓冲区中 for(y=0; y128){∥128是中值 打白点 oat)(num-2550);∥)计算误差 pIntAt=0;/打黑点 um,∥计算误差 if(xbi. bitwidth-1){∥注意判断边界
{ MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); return FALSE; } IntBufSize=(DWORD)bi.biHeight*LineBytes*sizeof(int); if((hIntBuf=LocalAlloc(LHND,IntBufSize))==NULL) //int 类型的缓冲区 { MessageBox(hWnd,"Error alloc memory!","Error Message",MB_OK| MB_ICONEXCLAMATION); LocalFree(hTempImgData); return FALSE; } lpImgData=(LPBITMAPINFOHEADER)GlobalLock(hImgData); lpTempImgData=(LPBITMAPINFOHEADER)LocalLock(hTempImgData); lpIntBuf=(int *)LocalLock(hIntBuf); //拷贝头信息 memcpy(lpTempImgData,lpImgData,OffBits); //将图象数据拷贝到 int 类型的缓冲区中 for(y=0;y 128 ){ //128 是中值 *lpIntPtr=255; //打白点 e=(float)(num-255.0); //计算误差 } else{ *lpIntPtr=0; //打黑点 e=(float)num; //计算误差 } if(x<bi.biWidth-1){ //注意判断边界
f=(float) (IpIntPtr+1) ( lpInt Ptr+1)=(int)f,∥向左传播 if(y<bi. heIght-1){∥注意判断边界 =(float)*(IpIntPtr-Line Bytes) f+=(foat(3.08.0)*e), ( IpIntPtr-LineBytes))=(int)f,∥向下传播 f=(float)*(IpIntPtr-Line Bytes+1) ( IpIntPtr- LineBytes+1)=(int)f,∥向右下传播 人int类型的缓冲区拷贝到char类型的缓冲区 for(y=0; y<bi. biHeight; y++)( IpTempPtr=(char * )Ip Templ lpIntPtr=(int *)lpInt Buf+(bi. biHeight-l-y)*Line Bytes, for(x=0; X<bi bi Width, x++)i tempnum=*(lpIntPtr++) 55)te *(pTempPtr++ (unsigned char )tempnum if(hBitmap l=NULL) hDc=GetDC(hWnd) ∥产生新的位图 hBitmap=CreateDIBitmap(hDc, (LPBITMAPINFOHEADER)lpTemplmg Data, (LONG)CBM INIT sizeof( BITMAPINFOHEADER)+ Num Colors*sizeof( RGBQUAD) (LPBITMAPINFO)IpTemplmg Data DIB RGB COLORS) hf- creat("c: I\steinberg. bmp",O Iwrite(hf, (LPSTR)&bf, sizeof( BITMAPFILEHEADER))
f=(float)*(lpIntPtr+1); f+=(float)( (3.0/8.0) * e); *(lpIntPtr+1)=(int)f; //向左传播 } if(y255) tempnum=255; else if (tempnum<0) tempnum=0; *(lpTempPtr++)=(unsigned char)tempnum; } } if(hBitmap!=NULL) DeleteObject(hBitmap); hDc=GetDC(hWnd); //产生新的位图 hBitmap=CreateDIBitmap(hDc,(LPBITMAPINFOHEADER)lpTempImgData, (LONG)CBM_INIT, (LPSTR)lpTempImgData+ sizeof(BITMAPINFOHEADER)+ NumColors*sizeof(RGBQUAD), (LPBITMAPINFO)lpTempImgData, DIB_RGB_COLORS); hf=_lcreat("c:\\steinberg.bmp",0); _lwrite(hf,(LPSTR)&bf,sizeof(BITMAPFILEHEADER));
Iwrite(hf, (LPSTR)Ip Data, BufSize) ∥释放内存和资源 ReleaseD(hWnd, hDc) LocalUnlock(hTemplmg Data); ocalFree(hTemplmg Data LocalUnlock(hInt Buf); LocalFree(hInt Buf) 要注意的是,误差传播有时会引起流水效应,即误差不断向下,向右累加传播。解决的办法 是:奇数行从左到右传播,偶数行从右到左传播。 43将bmp文件转换为txt文件 在讲图案化技术时,我突然想到了一个非常有趣的应用,那就是bmp2wxt。如果你喜欢上 BBS(电子公告牌系统),你可能想做一个花哨的签名档。瞧,这是我好朋友 Casper的签名档 (见图48),胖乎乎的,是不是特别可爱? ssssssssssssssssssssssssscc dssssssssssssssssssssssssssssssc dssssssssssssssssss, s ssssssssssssssssssssssssssssssi Sh SssSSsssssssssssssssssssF, sssh, ?sssssssssshSF sss555F:??$55:)$s5$P",,SF ?s55555h,:P'Js$F,s, ?Ss555cds5$h,“,ds sssssssssssssssssssssssssscssSsh sssssssbcccc,, ??ssssssccf"??ssss??sssssss s5555h?555$h:.dSS5FP °sss5hc, cassis55P ssssssssssssssss: SSSSSSSSS???????" 图48 Casper的签名档 你仔细观察一下,就会发现,这是一幅全部由字符组成的图,因为在BBS中只能出现文本 的东西。那么,这幅图是怎么做出来的呢?难道是自己一个字符一个字符拼出来的。当然不 是了,有一种叫bmp2t的应用程序(2的发音和“to”一样,所以如此命名),能把位图文件 转换成和图案很相似的字符文本。是不是觉得很神奇?其实原理很简单,用到了和图案化技 术类似的思想:首先将位图分成同样大小的小块,求出每一块灰度的平均值,然后和每个字
_lwrite(hf,(LPSTR)lpTempImgData,BufSize); _lclose(hf); //释放内存和资源 ReleaseDC(hWnd,hDc); GlobalUnlock(hImgData); LocalUnlock(hTempImgData); LocalFree(hTempImgData); LocalUnlock(hIntBuf); LocalFree(hIntBuf); return TRUE; } 要注意的是,误差传播有时会引起流水效应,即误差不断向下,向右累加传播。解决的办法 是:奇数行从左到右传播,偶数行从右到左传播。 4.3 将 bmp 文件转换为 txt 文件 在讲图案化技术时,我突然想到了一个非常有趣的应用,那就是 bmp2txt。如果你喜欢上 BBS(电子公告牌系统),你可能想做一个花哨的签名档。瞧,这是我好朋友 Casper 的签名档 (见图 4.8),胖乎乎的,是不是特别可爱? 图 4.8 Casper 的签名档 你仔细观察一下,就会发现,这是一幅全部由字符组成的图,因为在 BBS 中只能出现文本 的东西。那么,这幅图是怎么做出来的呢?难道是自己一个字符一个字符拼出来的。当然不 是了,有一种叫 bmp2txt 的应用程序(2 的发音和“to”一样,所以如此命名),能把位图文件 转换成和图案很相似的字符文本。是不是觉得很神奇?其实原理很简单,用到了和图案化技 术类似的思想:首先将位图分成同样大小的小块,求出每一块灰度的平均值,然后和每个字