65.9K
CodeProject 正在变化。 阅读更多。
Home

适用于 C/C++ 的简单两文件图形库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.85/5 (32投票s)

2012年4月11日

CPOL

5分钟阅读

viewsIcon

95878

downloadIcon

3263

一个用于调试图形功能不足的应用程序的两文件图形库。

使用此库创建的示例图像

363908/ezdib.png

有限资源示例

363908/ezdib_mono_dotmatrix.png

下载次数

引言

我总是需要为控制台、服务、嵌入式或其他本身不具备图形能力的应用程序添加简单的图形输出以用于调试。所以我汇集了几个函数来轻松实现这一点。

在本文中,我将提供一个简单的图形库的代码和解释,该库可以在最有限的条件下运行。

因此,我认为这个库将独一无二且有价值的功能是...

  • 可用于 CC++
  • 体积小巧,完全包含在两个文件中,即 ezdib.hezdib.c。只需将其放入即可使用。
  • 支持保存 DIB (.bmp) 文件。
  • 提供绘制像素、圆形、矩形、填充、文本以及原始缓冲区访问的基本函数。从提供的图像中可以看出其大部分功能。
  • 完全独立。这包括字体函数和字体。
  • 易于定制和扩展,包括添加新字体。

创建空白图像。

ezd_create() 函数为图像分配内存,并通过 HEZDIMAGE 类型的句柄不透明地返回所有图像详细信息。以下代码创建一个 640 x 480 的图像,每像素 24 位。此库仅支持 1、24 和 32 位像素深度。我认为这提供了良好的功能,同时又不损害简洁性。

请注意负高度。在很久以前,有人决定将 Windows 图像存储为倒置的。使用负高度将使图像在内存中正确存储。此库中的所有函数都可以正常工作,但请注意,Y 轴上的坐标将根据您的选择而反转。

	// Create an image
	HEZDIMAGE hDib = ezd_create( 640, -480, 24 );

	// Fill in the background with a dark gray color
	ezd_fill( hDib, 0x606060 );
	
	// Save a test image
	ezd_save( hDib, "test.bmp" );

	// Free resources
	ezd_destroy( hDib );

绘图和文本函数。

我试图使这些函数尽可能简单,同时保持它们相当快。我希望您能轻松理解这些函数的作用,并能毫无问题地添加自己的功能。

线条绘制使用增量划分或 Bresenham 线条算法 实现。洪水填充是一个简单的 四向填充。其余的不足以提及,但所有细节都在代码中;)

	// Draw random green line
	ezd_line( hDib, 100, 100, 200, 200, 0x00ff00 ),

	// Random yellow box
	ezd_fill_rect( hDib, 300, 200, 350, 280, 0xffff00 );

	// Draw random white dot
	ezd_set_pixel( hDib, 50, 50, 0xffffff );

	// Rectangle
	ezd_rect( hDib, 50, 50, 100, 100, 0x000000 );

	// Circle
	ezd_circle( hDib, 100, 100, 10, 0x000000 );

	// Fill the circle
	ezd_flood_fill( hDib, 100, 100, 0x000000, 0x808080 );

可能是最有趣的绘图函数是文本函数。以下代码示例了创建字体并将文本绘制到图像中。

	// Create a medium font
	HEZDFONT hFont = ezd_load_font( EZD_FONT_TYPE_MEDIUM, 0, 0 );

	// Draw some familiar text
	ezd_text( hDib, hFont, "Hello World!", -1, 10, 10, 0xffffff );

	// Free the memory
	ezd_destroy_font( hFont );
	

字体采用非常简单的基于栅格的格式。仅支持 8 位 ASCII 字符。格式如下。

	| [character] | [width] | [height] | [bitmap] ~ |

  • 字符 - 一个字节代表字形字符
  • 宽度 - 一个字节指定字符的位宽度
  • 高度 - 一个字节指定字符的位高度
  • 位图 - 每像素一位的位图,填充到一个字节

这是实际字体定义的一个摘录。您可以直接将字节数组的指针传递给 ezd_load_font() 函数。如果未定义 EZD_STATIC_FONTS,则 ezd_load_font() 将复制字体表、索引字形并返回一个句柄。如果定义了 EZD_STATIC_FONTS,则该函数将简单地返回您传递的指针,并将其转换为 HEZDFONT 类型。

// Example font map
static const unsigned char my_font [] =
{
	// Default glyph
	'.', 1, 6,	0x08,

	// Tab width
	'\t', 8, 0,

	// Space
	' ', 3, 0,

	'!', 1, 6,	0xea,
	'@', 4, 6,	0x69, 0xbb, 0x87,
	'#', 5, 6,	0x57, 0xd5, 0xf5, 0x00,
	'$', 5, 6,	0x23, 0xe8, 0xe2, 0xf8,
	...

	'0', 3, 6,	0x56, 0xd4, 0x31,
	'1', 2, 6,	0xd5, 0x42,
	'2', 4, 6,	0xe1, 0x68, 0xf0,
	...

	'A', 4, 6,	0x69, 0xf9, 0x90,
	'B', 4, 6,	0xe9, 0xe9, 0xe0,
	'C', 4, 6,	0x78, 0x88, 0x70,
	...
};

...

HEZDFONT hFont = ezd_load_font( my_font, 0, 0 );

嵌入式系统

我添加了几个功能来专门解决嵌入式设计面临的问题。对于微小的 8051 系统,此库可能仍然有点臃肿,但只要您的系统比较合理,您应该能够挤进这个库。如果您启用了所有以下宏,您基本上将只剩下一个纯粹的 C 实现。

以下宏在 ezdib.c 中进行了解释,并可以在其中定义,但是,最好的做法可能是将其定义在您的 make 或项目文件中。

#define EZD_STATIC_FONTS

定义此宏将消除字形索引。这意味着字体渲染可能会稍慢,但无需分配额外的内存。

#define EZD_NO_MEMCPY

如果您没有 string.h,则可以启用此宏,库将使用内部替换来处理 memcpymemset。这些可能速度会稍慢一些。

#define EZD_NO_ALLOCATION

您可以启用此宏来禁用 malloccallocfree 的使用。然后,您需要使用 ezd_initialize()ezd_set_image_buffer() 传入静态缓冲区。EZD_HEADER_SIZE 宏将指示图像头所需的最小空间。请参见下面的代码。

	// User buffer
	const int w = 320, h = 240;
	char user_header[ EZD_HEADER_SIZE ];
	char user_image_buffer[ w * h / 8 ];

	// Initialize image header
	hDib = ezd_initialize( user_header, sizeof( user_header ), w, -h, 1,
                               EZD_FLAG_USER_IMAGE_BUFFER );
	if ( !hDib )
		return -1;

	// Set custom image buffer
	if ( !ezd_set_image_buffer( hDib, user_image_buffer, sizeof( user_image_buffer ) ) )
		return -1;

#define EZD_NO_FILES

您的嵌入式系统可能不支持甚至没有文件系统。使用此宏禁用文件系统输出。

#define EZD_NO_MATH

如果您没有 math.h。这由圆形绘制函数使用。

单色图像 / 每像素一位的颜色深度。

此库还支持每像素一位的颜色深度。因此,如果您使用的是单色 LCD 显示屏,并且可以容纳备用缓冲区,则可以使用此模式渲染图像。也许如果您的内存是映射的,您可以使用 ezd_set_image_buffer() 直接渲染到设备。

渲染到单色图像的示例

363908/ezdib_mono.png

无缓冲图形输出

最糟糕的情况是什么?您有一个微小的处理器,没有空间用于备用缓冲区。或者您只想使用某种串行显示设备,如点阵屏。

只要您能提供一个接受 X 和 Y 坐标、颜色并进行渲染的“设置像素”函数,您就可以使用此库。

这是一个使用自定义设置像素函数来模拟点阵显示屏的示例。

	typedef struct _SDotMatrixData
	{
		int w;
		int h;
		HEZDIMAGE pDib;
	} SDotMatrixData;

	int dotmatrix_writer( void *pUser, int x, int y, int c, int f )
	{
		HEZDIMAGE hDib = (HEZDIMAGE)pUser;
		if ( !hDib )
			return 0;
			
		...
		
		return 1;
	}

	SDotMatrixData dmd;
	const int w = 640, h = 480;
	HEZDIMAGE hDmd;
	
	printf( "Creating dotmatrix.bmp\n" );
	
	// Create a 'fake' dot matrix display
	hDmd = ezd_create( w, -h, 24, 0 );
	if ( !hDmd )
		return -1;
	
	// Give our dot matrix display a black background
	ezd_fill( hDmd, 0 );

	// Create video 'driver'
	hDib = ezd_create( w, -h, 1, EZD_FLAG_USER_IMAGE_BUFFER );
	if ( !hDib )
		return -1;

	// Set pixel callback function
	ezd_set_pixel_callback( hDib, &dotmatrix_writer, hDmd );

	// Draw into hDib
	...
	

示例“点阵设备”输出

363908/ezdib_dotmatrix.png

无缓冲 ASCII 输出

作为一个最后的有趣示例,这里有一个完整的程序,它实现了一个将输出渲染到 ASCII 字符串缓冲区的设置像素函数。

	typedef struct _SAsciiData
	{
		int sw;
		unsigned char *buf;
	} SAsciiData;

	int ascii_writer( void *pUser, int x, int y, int c, int f )
	{
		SAsciiData *p = (SAsciiData*)pUser;
		if ( !p )
			return 0;
		
		unsigned char ch = (unsigned char)( f & 0xff );
		if ( ( '0' <= ch && '9' >= ch )
			 || ( 'A' <= ch && 'Z' >= ch )
			 || ( 'a' <= ch && 'z' >= ch ) )
			
			// Write the character
			p->buf[ y * p->sw + x ] = (unsigned char)f;

		else
			
			// Write the 'color'
			p->buf[ y * p->sw + x ] = (unsigned char)c;
		
		return 1;
	}

	int main( int argc, char* argv[] )
	{
		int b, x, y;
		HEZDIMAGE hDib;
		HEZDFONT hFont;
		SAsciiData ad;
		const int w = 44, h = 20;
		char ascii[ ( w + 1 ) * h + 1 ];
		char user_header[ EZD_HEADER_SIZE ];
			
		hDib = ezd_initialize( user_header, sizeof( user_header ),
                                       w, -h, 1, EZD_FLAG_USER_IMAGE_BUFFER );
		if ( !hDib )
			return -1;
			
		// Null terminate
		ascii[ ( w + 1 ) * h ] = 0;
		
		// Fill in new lines
		for ( y = 0; y < h - 1; y++ )
			ascii[ y * ( w + 1 ) + w ] = '\n';
		
		// Set pixel callback function
		ad.sw = w + 1; ad.buf = ascii;
		ezd_set_pixel_callback( hDib, &ascii_writer, &ad );

		// Fill background with spaces
		ezd_fill( hDib, ' ' );
		
		// Border
		ezd_rect( hDib, 0, 0, w - 1, h - 1, '.' );
		
		// Head
		ezd_circle( hDib, 30, 10, 8, 'o' );

		// Mouth
		ezd_arc( hDib, 30, 10, 5, 0.6, 2.8, '-' );
		
		// Eyes
		ezd_set_pixel( hDib, 28, 8, 'O' );
		ezd_set_pixel( hDib, 32, 8, 'O' );
		
		// Nose
		ezd_line( hDib, 30, 10, 30, 11, '|' );
		
		// Draw some text
		hFont = ezd_load_font( EZD_FONT_TYPE_SMALL, 0, 0 );
		if ( hFont )
			ezd_text( hDib, hFont, "The\nEnd", -1, 4, 4, '#' );
		
		if ( hFont )
			ezd_destroy_font( hFont );

		if ( hDib )
			ezd_destroy( hDib );

		// Show our buffer
		printf( "%s\n", ascii );
		
		return 0;
	}
	
............................................
.                                          .
.                             o            .
.                          ooooooo         .
.   TTT  h  h  eeee      oo       oo       .
.    T   h  h  e        oo         oo      .
.    T   hhhh  eee      o           o      .
.    T   h  h  e       o             o     .
.    T   h  h  eeee    o    O   O    o     .
.                      o             o     .
.                     oo      |      oo    .
.   EEEE  n  n  ddd    o      |      o     .
.   E     nn n  d  d   o  -       -  o     .
.   EEE   n nn  d  d   o   -     -   o     .
.   E     n  n  d  d    o   -----   o      .
.   EEEE  n  n  ddd     oo         oo      .
.                        oo       oo       .
.                          ooooooo         .
.                             o            .
............................................

愿望清单

我希望您发现代码有用且灵活。我想添加到项目中的一些功能是。

  • AVI、动画 GIF 或其他视频输出。
  • 字体编辑脚本,可能用 JavaScript 编写?我用来创建这些字体的脚本有点麻烦。
© . All rights reserved.