在 Windows 中绘制条形码第一部分 - Code 39





5.00/5 (19投票s)
一篇关于将 Code 39 条形码绘制到屏幕或剪贴板的文章。
引言
我最近在工作中的一个项目需要将条形码字符写入旧式照相排版机的字体文件中。这段经历启发我开始了一个副项目,编写一些代码,在给定正确输入的情况下,在 Windows 屏幕上渲染条形码。本系列文章就是该项目的成果。
Code 39 基础知识
第一篇文章将介绍如何在 Windows 屏幕上绘制 Code 39 条形码。在讨论代码之前,我们需要了解一些关于 Code 39 条形码符号系统的基本事实。Code 39 是第一个开发的字母数字符号系统,在工业领域得到了广泛应用。Code 39 具有两种不同的元素宽度:宽和窄,通常通过指定窄宽度和宽窄比来定义。每个 Code 39 字符由五个条形和四个空格组成,共九个元素。在九个元素中,有三个是宽的,六个是窄的,因此得名 Code 39(3 of 9)。每个字符后面都有一个字符间隙,通常等于窄元素的宽度。Code 39 符号系统的 44 个字符如下所示:
字符 | 模式 (bsbsbsbsb) |
1 | wnnwnnnnw |
2 | nnwwnnnnw |
3 | wnwwnnnnn |
4 | nnnwwnnnw |
5 | wnnwwnnnn |
6 | nnwwwnnnn |
7 | nnnwnnwnw |
8 | wnnwnnwnn |
9 | nnwwnnwnn |
0 | nnnwwnwnn |
A | wnnnnwnnw |
B | nnwnnwnnw |
C | wnwnnwnnn |
D | nnnnwwnnw |
E | wnnnwwnnn |
F | nnwnwwnnn |
G | nnnnnwwnw |
H | wnnnnwwnn |
我 | nnwnnwwnn |
J | nnnnwwwnn |
K | wnnnnnnww |
L | nnwnnnnww |
M | wnwnnnnwn |
N | nnnnwnnww |
O | wnnnwnnwn |
P | nnwnwnnwn |
Q | nnnnnnwww |
R | wnnnnnwwn |
S | nnwnnnwwn |
T | nnnnwnwwn |
U | wwnnnnnnw |
V | nwwnnnnnw |
W | wwwnnnnnn |
X | nwnnwnnnw |
Y | wwnnwnnnn |
Z | nwwnwnnnn |
- | nwnnnnwnw |
. | wwnnnnwnn |
SPACE | nwwnnnwnn |
* | nwnnwnwnn |
$ | nwnwnwnnn |
/ | nwnwnnnwn |
+ | nwnnnwnwn |
% | nnnwnwnwn |
Code 39 消息以星号开头和结尾,该星号是此符号系统的起始/停止码。下面是一个示例 Code 39 消息“DATA”,其中包含起始和停止码。
条形码位图工作区
Barcode Bitmap 工作区中有三个不同的项目。第一个也是最重要的项目是 bblib 项目。该项目是一个静态库,所有不同类型的条形码绘制代码都存在于其中。这也是本系列文章讨论的主要代码部分。Barcode Bitmap 工作区的另一个项目是 bbdll 项目。该项目只是一个围绕 bblib 静态库的常规 DLL 包装器。Barcode Bitmap 工作区的最后一个项目是 DLL 客户端项目。该项目是一个简单的基于对话框的应用程序,它调用 bbdll DLL 在对话框中绘制条形码,或将条形码作为 Windows 位图放入剪贴板。
基类 CBarcode
本系列文章中讨论的所有条形码类型的基类是 CBarcode
类。类声明如下所示。
class CBarcode { public: CBarcode(); void LoadData(CString csMessage, double dNarrowBar, double dFinalHeight, HDC pDC, int nStartingXPixel, int nStartingYPixel, double dRatio = 1.0); virtual void DrawBitmap() = 0; virtual void BitmapToClipboard() = 0; virtual ~CBarcode(); long GetBarcodePixelWidth(); long GetBarcodePixelHeight(); protected: CString m_csMessage; HDC m_hDC; long m_nFinalBarcodePixelWidth; long m_nNarrowBarPixelWidth; long m_nPixelHeight; long m_nStartingXPixel; long m_nStartingYPixel; long m_nSymbology; long m_nWideBarPixelWidth; virtual void DrawPattern(CString csPattern) = 0; };
关于 CBarcode
类有几点需要注意。首先要注意,它具有包含绘制条形码消息所需的所有有用数据的成员变量。这些数据包括窄元素的像素宽度、宽元素的像素宽度、消息和符号系统。其次,该类具有包含条形码消息输出信息的成员变量。这些数据包括设备上下文句柄以及起始 X 和 Y 像素。第三,该类包含一些公共成员函数,用于通过加载数据来初始化类,并获取有关条形码消息的信息,即其像素高度和宽度。第四,该类有几个抽象成员函数,使得该类成为一个抽象基类。任何派生自 CBarcode
的类都将需要实现这些函数。
CCode39 类
CCode39
类是用于绘制 Code 39 条形码的类。类声明如下所示。
class CCode39 : public CBarcode { public: void BitmapToClipboard(); void DrawBitmap(); CCode39(); virtual ~CCode39(); private: void DrawPattern(CString csPattern); CString RetrievePattern( char c ); };
该类有两个公共函数 BitmapToClipboard()
和 DrawBitmap()
,此外它还从 CBarcode
类继承了 LoadData()
函数。使用该类的步骤很简单:声明该类的一个实例,调用 LoadData()
初始化类数据,然后调用 BitmapToClipboard()
(如果您想将条形码的位图放到剪贴板上),或者调用 DrawBitmap()
来绘制条形码消息。
将条形码绘制到设备上下文
以下代码片段是使用 DrawBitmap()
的示例。
CString csMessage; double dNarrowBar,dHeight, dRatio; HDC pDC; long nStartingXPixel, nStartingYPixel; CCode39 oBarcode; // assign variable values here // call LoadData and draw the barcode oBarcode.LoadData(csMessage,dNarrowBar,dHeight,pDC,nStartingXPixel, nStartingYPixel,dRatio); oBarcode.DrawBitmap();
将条形码绘制到剪贴板
以下代码片段是使用 BitmapToClipboard()
的示例。
HDC hDC = NULL; double dNarrowbar,dHeight,dRatio; CCode93 oBarcode; // assign variable values here // call LoadData and BitmapToClipboard() oBarcode.LoadData(csMessage,dNarrowBar,dHeight,hDC,0,0,dRatio); oBarcode.BitmapToClipboard();
请注意,在使用 BitmapToClipboard()
函数时,您可以在 LoadData()
调用中传递一个空设备上下文句柄以及零作为起始 X 和 Y 像素。显然,起始 X 和 Y 像素在剪贴板上没有意义,但空设备上下文句柄呢?这个问题可以通过查看 BitmapToClipboard()
函数中的这段代码片段来回答。
CDC memDC; memDC.CreateCompatibleDC(NULL);
因此,BitmapToClipboard()
函数通过调用 memDC.CreateCompatibleDC(NULL)
函数来创建自己的内存设备上下文。快速查阅 MSDN 文档会发现,如果将 NULL 值传递给 CreateCompatibleDC
,则创建的设备上下文与屏幕兼容。
CBarcode::LoadData() 详细信息
CBarcode::LoadData()
的参数需要进一步解释,这里是合适的地方。第一个参数 csMessage
只是您希望绘制为 Code 39 条形码的消息。下一个参数 dNarrowBar
是窄元素的宽度(以英寸为单位)。参数 dHeight
是条形码的高度(以英寸为单位)。参数 pDC
是将要绘制条形码的设备上下文句柄。接下来的两个参数 nStartingXPixel
和 nStartingYPixel
定义了绘制条形码的起始坐标。最后一个参数 dRatio
是宽/窄元素宽度的比率。如果您还记得上面 CBarcode
类的声明,您会记得它以像素为单位存储所有宽度和高度信息,并且它存储窄元素宽度和宽元素宽度,而不是窄元素宽度和宽/窄元素宽度比。显然,CBarcode::LoadData()
在后台进行了一些转换工作。
该转换工作的第一个步骤是获取 X 轴和 Y 轴 DPI,这通过以下代码完成,取自 CBarcode::LoadData()
。
CDC tempDC; tempDC.Attach(m_hDC); nXAxisDpi = tempDC.GetDeviceCaps(LOGPIXELSX); nYAxisDpi = tempDC.GetDeviceCaps(LOGPIXELSY); tempDC.Detach();
一旦有了 X 和 Y 轴 DPI,就可以计算像素高度、窄元素像素宽度和宽元素像素宽度,如下面的代码片段所示。
// load the final attributes that depend on the device context m_nPixelHeight = (int)((nYAxisDpi*dFinalHeight)+0.5); m_nNarrowBarPixelWidth = (int)((nXAxisDpi*dNarrowBar)+0.5); m_nWideBarPixelWidth = (int)(dRatio*m_nNarrowBarPixelWidth);
注意计算窄元素像素宽度和宽元素像素宽度时的四舍五入效应。窄元素的宽度有一个像素的下限,因此您可以生成的条形码受到输出设备的物理限制。
接下来,您可以计算最终的条形码像素宽度,此操作特定于符号,下面列出了 Code 39 的代码摘录。
// get final character width nTemp = m_csMessage.GetLength() + 2; // add message m_nFinalBarcodePixelWidth = nTemp * ((3*m_nWideBarPixelWidth) + (7*m_nNarrowBarPixelWidth));
上面的代码通过将消息长度加两个字符(用于起始码和停止码),然后将字符总数乘以每个字符的像素长度来计算最终的条形码像素宽度。请注意,每个字符的总长度使用窄元素宽度的 7 倍来容纳字符间隙。
CCode39::DrawBitmap() 细节
DrawBitmap()
函数是绘制每个消息字符的地方。以下是 CCode39::DrawBitmap()
函数的列表。
void CCode39::DrawBitmap() { int i; CString csCurrentPattern; // draw start character, an asterisk DrawPattern(RetrievePattern('*')); // draw each character in the message for (i=0;i<m_csMessage.GetLength();i++) DrawPattern(RetrievePattern(m_csMessage.GetAt(i))); // draw stop character, also an asterisk DrawPattern(RetrievePattern('*')); return; }
CCode39::DrawBitmap()
函数开始时绘制起始字符星号。然后代码遍历消息中的每个字符并绘制每个字符。最后,代码绘制停止字符,同样是星号。这里使用了两个私有成员函数。CCode39::DrawPattern()
绘制传递给它的模式,模式是一个 CString
,形式为“wnnwnnnnw”(字符“1”),类似于上面提到的字符数据。CCode39::RetrievePattern()
basically 是一个巨大的 switch 语句,检索任何合法的 Code 39 字符传递给它的模式。请注意,从 CCode39::RetrievePattern()
返回的每个字符模式的末尾都额外添加了一个“n”来添加字符间隙。
CCode39::DrawPattern() 细节
CCode39::DrawPattern()
函数在传递的设备上下文中绘制单个 Code 39 条形码字符。CCode39::DrawPattern()
函数如下所示。
void CCode39::DrawPattern( CString csPattern ) { int i,nXPixel,nYPixel,nTempWidth; CDC oDC; // attach to the device context oDC.Attach(m_hDC); // initialize X pixel value nXPixel = m_nStartingXPixel; for (i=0;i<csPattern.GetLength();i++) { // decide if narrow or wide bar if (csPattern.GetAt(i)=='n') nTempWidth = m_nNarrowBarPixelWidth; else nTempWidth = m_nWideBarPixelWidth; // X value for loop for (nXPixel=m_nStartingXPixel; nXPixel<m_nStartingXPixel+nTempWidth; nXPixel++) { // Y value for loop for (nYPixel = m_nStartingYPixel; nYPixel<m_nStartingYPixel+m_nPixelHeight; nYPixel++) { // if this is a bar if (i%2==0) oDC.SetPixelV(nXPixel,nYPixel,COLORBLACK); else oDC.SetPixelV(nXPixel,nYPixel,COLORWHITE); } } // advance the starting position m_nStartingXPixel+= nTempWidth; } // detach from the device context oDC.Detach(); return; }
CCode39::DrawPattern()
函数基本上是三个循环。最外层循环遍历模式中的每个字符(nnwnnnnww)。中间循环遍历当前窄元素或宽元素的每个 X 像素。最内层循环遍历当前 X 像素的每个 Y 像素。三个循环的中心是一个简单的 if 语句,它决定我们是在绘制条形还是空格,并将当前像素设置为黑色或白色(条形或空格)。此函数重复用于起始字符、所有消息字符和停止字符,以绘制完整的 Code 39 条形码。
摘要
绘制 Code 39 条形码的内容就到这里。本系列的第二部分将介绍 Codabar 条形码的绘制。希望您觉得这个类库很有用。
参考
条形码书籍 - 读取、打印、指定和应用条形码及其他机器可读符号的综合指南 第 4 版
作者:Roger C. Palmer
版权所有 1989, 1991, 1995, 2001 Helmers Publishing, Inc.
ISBN 0-911261-13-3