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

用 C 语言编写的简单图形库,支持 BMP 和 WMF 导出

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.46/5 (7投票s)

2011年7月15日

CPOL

5分钟阅读

viewsIcon

34054

downloadIcon

1658

一个用于将图形输出导出为BMP或WMF的图形库。

引言

本文旨在提供一个简单的C语言图形库,用于替代旧的BASIC图形命令,如HPLOTHCOLOR。该库不提供屏幕图形功能,但能够将图形输出导出为BMP或WMF。我希望这个库能帮助那些想为简单物理模拟制作自己图形库的程序员。

背景

我当时在学习物理模拟,偶然发现了一本1984年出版的精彩书籍。作者使用Apple II BASIC实现了所有的模拟算法,并使用HPLOTHCOLOR来显示图形结果。因此,这带来了一些进一步研究的问题。旧的BASIC语言不再使用。像Turbo-C这样的C/C++编译器在没有DOSBox的情况下无法运行。我不想安装Python或GD库。我想要的是一个小型、可移植的图形库,能够为物理模拟生成栅格或矢量图形输出。

Python及其扩展包是一个很好的解决方案,但它不符合我的要求。GD库也不行,它只支持栅格图形输出。因此,我决定自己制作一个简单的图形库,能够将图形结果导出为BMP和WMF。

Using the Code

下面的BASIC代码实现了一个简单的运动。该程序在a = dv/dt = -x的情况下,绘制X(位置)与T(时间)的关系图。

'Simple motion with a = dv/dt = -x
 
 10  REM DY2
 15  REM X-T GRAPH OF DY1
 20  INPUT "SCALE OF T-AXIS ="; K
 30  INPUT "SCALE OF X-AXIS ="; L
 40  HGR : HCOLOR=3
 50  HPLOT 0, 40 TO 0, 140
 60  HPLOT 0, 90 TO 279, 90
 70  X0 = 1:V0 = 0:DT = 0.1
 80  X = X0:V = V0
 90  A = -X
 100 V = V + A * (DT/2)
 110 X = X + V * DT
 120 T = T + DT
 130 T1 = T * K:X1 = X * L
 140 IF T1 > 270 THEN END
 150 HPLOT T1, -X1 + 90
 160 A = -X
 170 V = V + A * DT
 180 GOTO 110
 
 ]RUN
 SCALE OF T-AXIS =5
 SCALE OF X-AXIS = 40

下面的C代码产生了相同的图形结果

#include <stdio.h>
#include "libtg.h" 

int main() 
{ 
   /* declare variables for the equation of simple motion */ 
   float x,v,a, x0=1, v0=0, t= 0,dt=0.1,k=5,l=40,x1=0,t1=0;

   /* fwid: width of a frame in inch unit
    fhgt: height of a frame in inch unit
    xwid: pixel width
    xhgt: pixel  height*/
   float fwid = 0, fhgt = 3;
   int xwid=279, xhgt = 180;

   /* declare variable for plotting */
   TG_Color pixel_color = TG_GetColor(TG_BLACK);
   TG_PlotZone_Ptr zone=NULL;
   TG_Workspace_Ptr workspace = TG_InitTGLibrary("Letter");

   if(!workspace)  return 1;
   zone = &workspace->zone;

   /* synchronize the frame size with the pixel dimension.
   Set the max and min of the world coord system
   as the min and max of the pixel coordinate system. */

   fwid = fhgt*((float)xwid/xhgt);
   TG_SetFrame(&workspace->zone.frame, 0, 0, fwid, fhgt, 0, xwid,  0, xhgt);

   TG_BMP_OpenExport(zone, "sm.bmp", xwid, xhgt, 1);
   TG_BMP_SetReverseWorldCoord(1);

   x = x0;   
   v = v0;
   a = -x;
   v + = a*dt/2; 

   while(1) {
      x += v*dt;
      t += dt;
      t1 = t*k;
      x1 = x*l;
      if(t1 > 270) break;
      a = -x;
      v += a*dt;
      zone->plot.putpixel1(zone, t1, -x1+90, pixel_color);
   }
 
   TG_BMP_CloseExport();
   TG_CloseTGLibrary(workspace);
 
   return 0;
}

坐标系

LibTG的默认坐标系是世界坐标系,原点位于左下角。然而,导出函数会将世界坐标系转换为WMF的逻辑(纸面)坐标系或BMP的像素坐标系。BMP和WMF的原点相同,都位于左上角。WMF和BMP坐标系之间的区别在于单位。WMF使用英寸作为单位,内部是twip,而BMP使用像素作为单位。您可以通过使用SetWindowsOrg来更改WMF坐标系的原点,但我在此不深入探讨。

通常,旧的BASIC模拟代码使用像素坐标系(例如:HPLOT T1, -X1 + 90)。因此,在任何绘图函数之前,必须调用XXXAPI_SetReverseWorldCoord(1)XXX可以是BMP或WMF。第一张图代表世界坐标系,下一张图显示了纸面或像素坐标系。LibTG中有两个映射过程:世界坐标到逻辑坐标,以及逻辑坐标到像素坐标。BMP导出需要两者,而WMF只需要世界坐标到逻辑坐标。逻辑坐标系是英寸单位的纸面坐标系的别名。详细解释WMF格式将花费很多时间。您可以在互联网上找到格式规范,或者查看LibTG的WMF导出部分。

//Example1: BMP export

TG_Point world, logical, pixel;
world.x = x, world.y = y;
World2Length(&&world, &&logical);
Length2Pixel(&&logical, &&pixel);
... putpixel(..., pixel.x, pixel.y, ...);

Example2: WMF export

TG_Point world, logical, pixel;
world.x = x, world.y = y;
World2Length(&&world, &&logical);
... putpixel(..., _to_twip(pixel.x), _to_twip(pixel.y), ...);

现在,让我们将图形导出为WMF。这很简单。您只需将BMP替换为WMF,并删除OpenExport(..., xwid, xhgt, 1)的最后三个参数。

... 

TG_WMF_OpenExport(zone, "sm.wmf");
TG_WMF_SetReverseWorldCoord(1); 

...
TG_WMF_CloseExport(); 
TG_CloseTGLibrary(workspace);
 
return 0;

通过插入三行代码,您可以添加一个符号。gsavegrestore函数可以保护线条或像素的属性,使其不受图形状态变化的影响。gsavegrestore仅对WMF导出有效。如果您只需要BMP,则无需使用gsavegrestore

...
zone->plot.putpixel1(zone, t1, -x1+90, pixel_color);

zone->plot.gsave();
zone->plot.symbol(zone, t1, -x1+90, TG_SYM_CIRCLE,.03, 
                  TG_GetColor(TG_RED),1 ,TG_GetColor(TG_BLACK),1,0.001);
zone->plot.grestore();

int skip=5, iskip=0;
TG_BMP_OpenExport(zone, "sms-skip.bmp", xwid, xhgt, 1); 
            ... 
zone->plot.putpixel1(zone, t1, -x1+90, pixel_color);

if((iskip%skip)) iskip++;
else {
    zone->plot.gsave();
    zone->plot.symbol(zone, t1, -x1+90, TG_SYM_CIRCLE,.03, 
            TG_GetColor(TG_RED),1 ,TG_GetColor(TG_BLACK),1,0.001);
    zone->plot.grestore();
    iskip = 1;
}

这里有一些有趣的例子,我只发布图片。请查看源代码以了解更多信息。这些图像绘制了一些均匀流中的势函数。这些主题通常在流体力学入门阶段学习。第一个是一个均匀流中的偶极子(BMP)。第二个是一个均匀流中的涡旋(BMP)。最后一个展示了板以一定迎角流动时的流动行为(BMP)。

我将BMP和WMF文件插入了Microsoft PowerPoint

我制作了一个简单的WMF分析器,可以将WMF文件中的元命令保存为文本格式。从头开始开发WMF库时,这非常有用且直观。该程序有三个按钮:WMF、OK和Cancel。如果单击WMF,将出现一个文件对话框。选择一个WMF文件,将自动创建一个同名且扩展名为".txt"的文本文件,或者单击OK按钮。不幸的是,我丢失了源代码,但保留了可执行程序。我无法将任何可执行程序上传到CodeProject。如果您需要它,请给我发邮件。

SMW.TXT
***************************************
*  Windows Meta File(16bit) Analyzer  *
*             2004. 2. 2.             *
*    written by Euisang Hwang         *
***************************************

Placeable Metafile Header
-------------------------------------
                    Hex    Dec       
-------------------------------------
DWORD MagicNumber : 0x9ac6cdd7 2596720087
WORD  Handle      : 000000     0
short Left        : 000000     0
short Top         : 000000     0
short Right       : 0x1711     5905
short Bottom      : 0x0ee2     3810
short Inch        : 0x04f6     1270
DWORD Reserved    : 0000000000 0
WORD  CheckSum    : 0x4a14     18964
-------------------------------------

Standard Metafile Header
-------------------------------------
                      Hex   Dex      
-------------------------------------
 WORD FileType      : 0x0001     1
 WORD HeaderSize    : 0x0009     9
 WORD Version       : 0x0300     768
DWORD FileSize      : 0x00008d07 36103
 WORD NumOfObjects  : 0x057c     1404
DWORD MaxRecordSize : 0x0000001e 30
 WORD NumOfParams   : 000000     0
--------------------------------------

Record size  : 5
API Function : 0x020b
API Prototype: DWORD SetWindowOrg (HDC,int,int)
Parameter 0  : 000000 0
Parameter 1  : 000000 0

Record size  : 5
API Function : 0x020c
API Prototype: DWORD SetWindowExt (HDC,int,int)
Parameter 0  : 0x0ee2 3810
Parameter 1  : 0x1711 5905

Record size  : 8
API Function : 0x02fa
API Prototype: HPEN CreatePenIndirect (LOGPEN FAR *)
Parameter 0  : PS_SOLID(0)
Parameter 1  : 1
Parameter 2  : 0
Parameter 3  :   0 (RED)
Parameter 4  :   0 (GREEN)
                 0 (BLUE)

..............................

Record size  : 3
API Function : 000000
API Prototype: (End) (End) (End)
---------------------
Total record size : 36085

关注点

我找不到任何用于创建WMF文件的库。能够从零开始编写纯C代码来制作WMF导出对我来说非常有趣。

如何编译

通过点击\Start\Programs\Microsoft Visual Studio\Visual Studio Tools\2005 Visual Studio 2005 Command Prompt快捷方式打开开发命令提示符。输入几个字:cl xxx.c libtg.c。就这样!

Setting environment for using Microsoft Visual Studio 2005 x86 tools.

C:\Program Files (x86)\Microsoft Visual Studio 8\VC>d:

D:\>cd programming\libtg

D:\programming\libtg>cl s1.c libtg.c
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 14.00.50727.762 for 80x86

Copyright (C) Microsoft Corporation.  All rights reserved.

s1.c
libtg.c
Generating Code...
Microsoft (R) Incremental Linker Version 8.00.50727.762
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:s1.exe
s1.obj
libtg.obj

D:\programming\libtg>s1

D:\programming\libtg>dir sms-skip.bmp      ----> or sms-skip.wmf
 Volume in drive D is DATA
 Volume Serial Number is 7CF4-A7A1

 Directory of D:\programming\libtg

07/13/2011  07:48 PM           151,254 sms-skip.bmp
               1 File(s)        151,254 bytes
               0 Dir(s)  142,672,297,984 bytes free

D:\programming\libtg>

或者,您可以在Visual Studio 2005/2010中创建一个项目,或打开Watcom C/C++。忽略由于双精度到单精度转换而产生的许多类型转换警告。

错误和进一步开发

  1. 我尚未为BMP导出实现粗线算法。
  2. 需要添加异常处理以确保安全使用。
  3. BMP导出的磁盘内存选项不起作用。
  4. 需要添加文本功能。
  5. 仍然存在许多错误。

结论

旧的物理模拟书籍使用了BASIC语言。对于图形输出,程序员必须选择其他方式。安装开源软件或开源库可能是一个不错的选择。然而,对于简单的物理模拟,并不需要出色的图形函数。只需一个C/C++编译器和简单的函数就足够了。LibTG将是满足栅格图形或矢量图形输出质量的良好替代方案。

历史

首先,请原谅我的英语。英语不是我的母语。但是,我认为C/C++或其他编程语言是通用的。这篇文章是我在CodeProject上的第一次尝试。我希望所有读者都能享受我的库。

© . All rights reserved.