我的程序既写又读颜色数组,比如
struct Image {
size_t width;
size_t height;
struct Color *data;
}
struct Color {
char r;
char g;
char b;
}
如何用C在屏幕上显示这样的数组?
图形渲染:
我习惯了win32和Borland C++环境,所以我坚持使用它,但与其他环境的差异大多只是在类名上。第一种方法:
- 控制台/文本模式
您可以使用文本图形(我认为是英文的ASCII艺术)。其中点由字符表示强度由或多或少填充的字符组成。通常有一个按强度排序的字符表,如" ..:+*#"
,并使用它来代替颜色。对于打印内容,您可以使用iostream
,比如cout << "text" << endl;
或printf
,我认为stdio
(我已经十多年没有使用老式控制台输出了)。
文本模式视频RAM(VRAM)从0B000:0000
开始。如果您有权限,您可以直接访问:
char far *scr = (char far*)0x0B0000000;
scr[0] = 'A'; // Print A to left upper corner
但是在Windows上,您可以忘记直接访问。
- VGA图形模式
(仅限DOS,而非Windows;这是对VGA硬件的直接访问)。这里有一个小例子:
// Turbo C++ for 16-bit real mode DOS
//==============================================================================
char far* scr; // VGA screen
const _sx= 320; // Physical screen size
const _sy= 200;
//==============================================================================
void gfxinit();
void cls();
void pnt(int x,int y,char c);
//==============================================================================
void gfxinit()
{
asm { mov ax,19 // This switches VGA to 320*200*256 color mode (fits inside a single 64 KB segment so no funny stuff is needed)
int 16
}
for (int i=0;i<256;i++) asm { // This overwrites 256 color palette with some BW gradients
mov dx,0x3C8
mov ax,i
out dx,al // Overwrite color al = i
inc dx
shr al,2 // al=al>>2
out dx,al // r,g,b or b,g,r not sure now
out dx,al // All values are 6-bit long, therefore the shr al,2
out dx,al
}
scr=(char far*)0xA0000000; // VRAM start address
}
//==============================================================================
void cls() // This clears the screen with zeros
{
asm { push es
mov ax,0xA000
mov es,ax
mov di,0x0000
sub ax,ax
mov cx,32000
rep stosw
pop es
}
}
//==============================================================================
void pnt(int x,int y,char c) // This draws a single point of color c
{
unsigned int adr;
if (x<_sx)
if (x>=0)
if (y<_sy)
if (y>=0)
{
y=y*_sx;
adr=x+y;
scr[adr]=c;
}
}
//==============================================================================
VESA访问类似,但必须处理段交叉和分页。下面是一个小型Turbo C++示例:
VESA.h
// Turbo C++, still 16-bit DOS,
// but using VESA calls to set modes instead of VGA registers
//==============================================================================
//=== Globals: =================================================================
//==============================================================================
char far* scr=(char far*)0xA0000000; // VGA/VESA memory pointer
int VESA_page,VESA_pages; // Actual page and total pages
int VESA_xs,VESA_ys,VESA_bpp; // Video mode properties
int VESA_page_xy[64]={-1,-1}; // Starting x,y for each page
const int VESAmodes[]= // Usable video modes table
{
320, 200, 8,0x150,
640, 480, 8,0x101,
800, 600, 8,0x103,
1024, 768, 8,0x105,
1280,1024, 8,0x107,
320, 200,16,0x10E,
640, 480,16,0x111,
800, 600,16,0x114,
1024, 768,16,0x117,
320, 200,32,0x10F,
640, 480,32,0x112,
800, 600,32,0x115,
0,0,0,0
};
//==============================================================================
//=== Headers: =================================================================
//==============================================================================
int VESAmode(int xs,int ys,int bpp); // Set video mode
void VESApage(int page); // Set page
void VESAexit(); // Return to VGA text mode
void VESAcls(); // Clear with 0
void VESApnt(int x,int y,unsigned int c); // Render 8/16 bpp point
void VESApnt32(int x,int y,int r,int g ,int b); // render 32bpp point
//==============================================================================
//=== Graphic: =================================================================
//==============================================================================
int VESAmode(int xs,int ys,int bpp)
{
int i,mode,x,y;
unsigned int adr0,adr,dx,dy;
// find video mode
for (i=0;VESAmodes[i];i+=4)
if (VESAmodes[i+0]==xs)
if (VESAmodes[i+1]==ys)
if (VESAmodes[i+2]==bpp)
break;
if (!VESAmodes[i]) return 0;
mode=VESAmodes[i+3];
VESA_xs=xs;
VESA_ys=ys;
VESA_bpp=bpp;
// Compute start x,y for each page>0
dx=bpp>>3;
dy=xs*dx;
VESA_pages=1;
for (adr=i=x=y=0;y<VESA_ys;y++)
{
adr0=adr;
adr+=dy;
if (adr0>adr)
{
while (adr>0) { adr-=dx; x--; }
while (x<0) { x+=VESA_xs; y--; }
VESA_page_xy[i]=x; i++;
VESA_page_xy[i]=y+1; i++;
VESA_pages++;
}
}
VESA_page_xy[i]=-1; i++;
VESA_page_xy[i]=-1; i++;
// Set video mode
asm {
mov bx,mode
mov ax,0x4F02
int 16
}
VESApage(0);
/*
// Set palette to grayscale
if (VESAbpp==8)
for (int i=0;i<256;i++) asm {
mov dx,0x3C8
mov ax,i
out dx,al
inc dx
shr al,2
out dx,al
out dx,al
out dx,al
}
*/
return 1;
}
//==============================================================================
void VESApage(int page)
{
int p=page;
asm {
mov dx,p
mov bx,0
mov ax,0x4f05
int 16
}
VESA_page=page;
}
//==============================================================================
void VESAexit()
{
asm {
// Wait for key press
mov ax,0
int 0x16
// VGA 80x25 text mode
mov ax,3
int 16
}
}
//==============================================================================
void VESAcls()
{
int i;
for (i=0;i<VESA_pages;i++)
{
VESApage(i);
asm {
push es
mov ax,0xA000
mov es,ax
mov di,0x0000
mov ax,0
mov cx,32000
rep stosw
pop es
}
}
}
//==============================================================================
void VESApnt(int x,int y,unsigned int c)
{
unsigned int adr;
int p;
// inside screen?
if ((x>=0)&&(x<VESA_xs))
if ((y>=0)&&(y<VESA_ys))
{
// Low 16 bit of address
adr=y;
adr*=VESA_xs;
adr+=x;
adr*=(VESA_bpp>>3);
// Page
for (p=0;VESA_page_xy[p+p+0]>=0;p++)
{
if (VESA_page_xy[p+p+1]>y) break;
if (VESA_page_xy[p+p+1]<y) continue;
if (VESA_page_xy[p+p+0]>x) break;
}
if (p!=VESA_page) VESApage(p);
// Render
scr[adr]=c;
if (VESA_bpp==16)
{
adr++; if (adr==0) VESApage(p+1);
scr[adr]=(c>>8);
}
}
}
//==============================================================================
void VESApnt32(int x,int y,int r,int g ,int b)
{
unsigned int adr;
int p;
// inside screen?
if ((x>=0)&&(x<VESA_xs))
if ((y>=0)&&(y<VESA_ys))
{
// Low 16 bit of address
adr=y;
adr*=VESA_xs;
adr+=x;
adr*=(VESA_bpp>>3);
// Page
for (p=0;VESA_page_xy[p+p+0]>=0;p++)
{
if (VESA_page_xy[p+p+1]>y) break;
if (VESA_page_xy[p+p+1]<y) continue;
if (VESA_page_xy[p+p+0]>x) break;
}
if (p!=VESA_page) VESApage(p);
// Render
scr[adr]=b; adr++; if (adr==0) VESApage(p+1);
scr[adr]=g; adr++; if (adr==0) VESApage(p+1);
scr[adr]=r;
}
}
//==============================================================================
//=== End. =====================================================================
//==============================================================================
main.cpp
//==============================================================================
//=== Includes: ================================================================
//==============================================================================
#include "vesa.h"
//==============================================================================
//=== Main: ====================================================================
//==============================================================================
void main()
{
if (!VESAmode(800,600,32)) return;
VESAcls();
int x,y;
unsigned int c;
for (y=0;y<VESA_ys;y++)
for (x=0;x<VESA_xs;x++)
{
if (VESA_bpp== 8)
{
c=x+y;
VESApnt(x,y,c);
}
if (VESA_bpp==16)
{
c=(x&31)+((y&63)<<5);
VESApnt(x,y,c);
}
if (VESA_bpp==32) VESApnt32(x,y,x,x+y,y);
}
VESAexit();
}
//==============================================================================
//=== End. =====================================================================
//==============================================================================
- GDI-可在Windows上使用
Canvas
是Windows上可视化组件的图形子组件。在Borland中是名为Canvas
的类TCanvas
。所有窗口都有它,PaintBoxes, Bitmaps, ...
。它是Windows和应用程序之间的GDI接口。它有像Pen, Brush, and Font
这样的子组件,用于线条、填充或文本纸、文本墨水。
Form1->Canvas->Pen->Color=clYellow;
Form1->Canvas->MoveTo(10,10);
Form1->Canvas->LineTo(100,150);
其中Form1
是我的VCL窗口。这个代码画了一条黄线。
GDI有许多类似Arc, Ellipse, Pixels[][],...
的功能。有关详细信息,请参阅IDE的内置帮助。
- GDI位图
这是一个特殊的对象。它是一个带有OS图形句柄(DC设备上下文)的位图。这允许位图类似于窗口,并可以访问GDI:
Graphics::TBitmap *bmp=new Graphics::TBitmap;
bmp->Width=100;
bmp->Height=100;
bmp->HandleType=bmDIB; // Allows use of ScanLine
bmp->PixelFormat=pf32bit; // 32-bit - the same as int so we can use int* for pixels pointer
这将创建一个VCL位图,并通过直接访问将其设置为100x100x32 bit
。现在,您可以访问ScanLine
属性。bmp->Canvas
也存在,所以你也可以做所有GDI的事情。
int *p=bmp->ScanLine[10]; // p = pointer to y=10 line of bitmap
p[20]=0; // Draw dot on x=20,y=10 color=0x00000000 which is black
int c = p[15]; // Read pixel x=15,y=10 from bitmap to c
小心将 这会将位图绘制到窗口中,这样您就可以实际看到它。 有很多,但使用最多的是OpenGL和DirectX。我更喜欢OpenGL,因为它实现起来更简单(至少对初学者来说),而且OpenGL是跨平台的,DirectX只是Windows。此外,当我开始编码时,没有任何DirecX。当我开始使用OpenGL时,所有供应商都将其包含在驱动程序中。现在,唯一仍然是最新的供应商是Nvidia和ATI(AMD)。它们之间几乎总是存在一些驱动程序问题,但总的来说,Nvidia更适合OpenGL(在DirectX实现中存在错误),ATI(仅限AMD版本)更适合DirectX(在OpenGL实现中存在缺陷)。但对于基本操作来说,你是可以的(问题会出现在更高级的函数上)。 英特尔(Intel)、SiS等厂商已经停止了在更新的OpenGL版本上的实现。至少,我不知道有什么驱动程序比OpenGL 3.3更好。 要开始使用OpenGL,请参见OpenGL获取设备上下文。 我强烈建议先从GDI+位图开始。你可以用它们做很多事情。我仍在使用它进行非复杂渲染 如前所述,我对Borland(VCL风格)友好,因此如果您使用不同的编译器/IDE,则更改GDI对象名称以对应您的环境。我认为Canvas是一样的,位图是 其他平台和东西x,y
留在位图中,否则将引发异常。颜色编码取决于pixelformat
,通常是0x00RRGGBB
或0x00BBGGRR
。我认为这种方法对你来说是最好的选择。此外,您可以将任何GDI对象绘制到任何其他GDIForm1->Canvas->Draw(0, 0, bmp);
HBitmap
,但最好查看您的帮助/文档。至少你知道该搜索什么。