eduwin 画布与精灵 API

画布

HWND create_canvas(HWND parent, int x, int y, int w, int h);

创建一个可绘制的画布控件,使用 GDI+ 作为后端。画布维护一个离屏位图,所有绘图操作都在位图上进行,窗口重绘时自动刷新。

重要:所有绘图操作必须在 show_window 之后调用!

show_window 会触发 WM_SIZE 消息,画布会销毁当前离屏位图并按实际客户区尺寸重建。如果在 show_window 之前绘图,内容会被清空。


绘图状态设置

void canvas_clear(HWND canvas, int r, int g, int b, int a);
void canvas_set_pen(HWND canvas, int r, int g, int b, int a, int width);
void canvas_set_brush(HWND canvas, int r, int g, int b, int a);
void canvas_set_font(HWND canvas, const WCHAR *family, int size);

r / g / b 为颜色的红、绿、蓝分量,范围 0–255。a 为透明通道(alpha),0 完全透明,255 完全不透明。

  • canvas_clear 用指定颜色填充整个画布(清除原有内容)
  • canvas_set_pen 设置轮廓画笔颜色、透明度和线宽(默认黑色 1px 不透明)
  • canvas_set_brush 设置填充画刷颜色和透明度(默认白色不透明)
  • canvas_set_font 设置文本字体和字号(默认 Microsoft YaHei 14)

绘图操作

void canvas_draw_line(HWND canvas, int x1, int y1, int x2, int y2);
void canvas_draw_rect(HWND canvas, int x, int y, int w, int h);
void canvas_fill_rect(HWND canvas, int x, int y, int w, int h);
void canvas_draw_ellipse(HWND canvas, int x, int y, int w, int h);
void canvas_fill_ellipse(HWND canvas, int x, int y, int w, int h);
void canvas_draw_text(HWND canvas, const WCHAR *text, int x, int y, int r, int g, int b, int a, int size);
void canvas_draw_image(HWND canvas, void *image, int x, int y, int w, int h);
  • canvas_draw_line / canvas_draw_rect / canvas_draw_ellipse — 用当前画笔绘制轮廓
  • canvas_fill_rect / canvas_fill_ellipse — 用当前画刷填充
  • canvas_draw_text — 在指定位置绘制文本,r g b a 为文字颜色和透明度,size 为字号(传 0 使用 canvas_set_font 设定的字号)
  • canvas_draw_image — 绘制由 load_image_from_file 返回的图像
HWND cv = create_canvas(win, 10, 10, 400, 300);

canvas_clear(cv, 255, 255, 255, 255);               // 白色不透明背景

canvas_set_pen(cv, 0, 0, 255, 255, 3);              // 蓝色 3px 不透明画笔
canvas_draw_rect(cv, 20, 20, 100, 80);              // 矩形边框

canvas_set_brush(cv, 255, 200, 200, 255);            // 粉色不透明画刷
canvas_fill_ellipse(cv, 140, 20, 100, 80);          // 填充椭圆

canvas_draw_line(cv, 20, 120, 300, 120);             // 直线

canvas_draw_text(cv, L"Hello 画布", 20, 140,         // 文字
                 0, 0, 0, 255, 20);

局部清除

void canvas_clear_rect(HWND canvas, int x, int y, int w, int h, int r, int g, int b, int a);

用颜色填充指定矩形区域(局部清除),a 为透明度。

// 在灰色区域中间挖一个透明洞
canvas_set_brush(canvas, 200, 200, 200, 255);
canvas_fill_rect(canvas, 10, 10, 200, 100);
canvas_clear_rect(canvas, 50, 30, 100, 40, 100, 200, 50, 255);

文件输出

void canvas_save_image(HWND canvas, const WCHAR *filepath);

将画布的当前内容保存为图像文件(根据扩展名自动选择编码器,支持 png/jpg/bmp 等)。

canvas_save_image(cv, L"output.png");   // 保存到文件

游戏开发扩展

帧循环

void canvas_start_timer(HWND canvas, int fps, void (*callback)(void *), void *data);
void canvas_stop_timer(HWND canvas);

canvas_start_timer 启动帧循环,fps 为每秒回调次数,callback 每帧调用一次。canvas_stop_timer 停止帧循环。

键盘输入

int canvas_is_key_down(HWND canvas, int vkey);
void canvas_on_key(HWND canvas, void (*down)(int), void (*up)(int));

canvas_is_key_down 实时检测按键是否按住,vkey 为虚拟键码(如 VK_LEFT'A')。canvas_on_key 注册按键按下 / 释放事件回调。

鼠标输入

void canvas_on_mouse(HWND canvas, void (*move)(int, int), void (*down)(int, int, int), void (*up)(int, int, int));
int canvas_get_mouse_x(HWND canvas);
int canvas_get_mouse_y(HWND canvas);
int canvas_is_mouse_down(HWND canvas, int btn);

canvas_on_mouse 注册鼠标移动 / 按下 / 释放回调。

  • move(x, y) — 鼠标移动
  • down(btn, x, y) — 鼠标按下,btn 0=左键, 1=中键, 2=右键
  • up(btn, x, y) — 鼠标释放

canvas_get_mouse_x/y 获取鼠标当前坐标。canvas_is_mouse_down 检测鼠标是否按住(btn 0=左, 1=右)。

坐标注意: 画布鼠标回调中的 x, y画布客户区坐标。如果需要与 show_context_menu 等需要屏幕坐标的函数配合使用,必须用 ClientToScreen 转换:

void on_mouse_down(int btn, int x, int y)
{
    POINT pt = {x, y};
    ClientToScreen(canvas, &pt);
    show_context_menu(ctx_menu, canvas, pt.x, pt.y);
}

画布尺寸

int canvas_get_width(HWND canvas);
int canvas_get_height(HWND canvas);

获取画布的实际像素尺寸。

游戏循环示例

void game_tick(void *data)
{
    canvas_clear(canvas, 0, 0, 0, 255);

    // WASD 控制方块
    static float px = 100, py = 100;
    if (canvas_is_key_down(canvas, 'W')) py -= 3;
    if (canvas_is_key_down(canvas, 'S')) py += 3;
    if (canvas_is_key_down(canvas, 'A')) px -= 3;
    if (canvas_is_key_down(canvas, 'D')) px += 3;

    canvas_set_brush(canvas, 0, 200, 0, 255);
    canvas_fill_rect(canvas, (int)px, (int)py, 40, 40);

    canvas_draw_text(canvas, L"按 ESC 退出", 5, 5, 128, 128, 128, 255, 14);
}

// 在 main 中:
canvas_start_timer(canvas, 60, game_tick, NULL);
canvas_on_key(canvas, NULL, NULL);  // 确保画布获得焦点

精灵系统

精灵(Sprite)是画布上可独立控制的图形对象。所有精灵内部都统一为位图(GpBitmap)处理——形状创建时自动渲染为位图,图片从文件加载后也是位图。后续所有操作(颜色叠加、变换等)都直接作用于位图数据。

形状类型常量

#define SHAPE_RECT      0   /* 矩形 */
#define SHAPE_ELLIPSE   1   /* 椭圆/圆 */
#define SHAPE_TRIANGLE  2   /* 三角形(朝上) */
#define SHAPE_DIAMOND   3   /* 菱形 */
#define SHAPE_PENTAGON  4   /* 正五边形 */
#define SHAPE_HEXAGON   5   /* 正六边形 */
#define SHAPE_STAR      6   /* 五角星 */
#define SHAPE_ARROW     7   /* 箭头(朝右) */
#define SHAPE_PIE       8   /* 扇形(缺右上角) */

精灵创建与销毁

Sprite *sprite_create_from_shape(int type, int w, int h);
Sprite *sprite_create_from_file(const WCHAR *filepath);
Sprite *sprite_create_from_image(void *image);
Sprite *sprite_clone(Sprite *s);
void sprite_destroy(Sprite *s);
  • sprite_create_from_shape(type, w, h) 创建指定形状和大小的精灵,type 为上述 SHAPE_* 常量之一
  • sprite_create_from_file 从图片文件创建精灵,尺寸自动匹配
  • sprite_create_from_imageload_image_from_file 返回的图像创建精灵(内部克隆副本,不直接引用外部指针)
  • sprite_clone 克隆精灵,内部共享原始图片备份(origin_image),克隆体有独立的工作副本(image
  • sprite_destroy 销毁精灵并释放资源

精灵变换

void sprite_set_pos(Sprite *s, float x, float y);
void sprite_move(Sprite *s, float dx, float dy);
void sprite_set_scale(Sprite *s, float sx, float sy);
void sprite_set_rotation(Sprite *s, float degrees);
void sprite_set_color(Sprite *s, int r, int g, int b, int a);
void sprite_set_image(Sprite *s, void *image);
  • sprite_set_pos 设置精灵左上角位置
  • sprite_move 相对当前位置移动
  • sprite_set_scale 缩放精灵(1.0 为原始大小)
  • sprite_set_rotation 旋转精灵(角度制),绕原点(默认中心)旋转
  • sprite_set_color 设置精灵的整体颜色叠加(正片叠底 Multiply 混色模式,透明像素不受影响)
  • sprite_set_image 更换精灵的图像(内部克隆为新副本,并更新原始图备份)

精灵属性读取

float sprite_get_x(Sprite *s);
float sprite_get_y(Sprite *s);
float sprite_get_w(Sprite *s);
float sprite_get_h(Sprite *s);
float sprite_get_sx(Sprite *s);
float sprite_get_sy(Sprite *s);

精灵绘制与碰撞

void sprite_draw(HWND canvas, Sprite *s);
int sprite_collide(Sprite *a, Sprite *b);
int sprite_hit_test(Sprite *s, float px, float py);

sprite_draw 将精灵绘制到指定画布,自动处理位置、缩放、旋转。sprite_collide AABB 碰撞检测,传入两个 Sprite*,返回 1 表示碰撞。sprite_hit_test 检测点 (px, py) 是否在精灵矩形内(含缩放)。

精灵事件

void on_sprite_click(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_down(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_up(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);
void on_sprite_hover(HWND canvas, Sprite *s, void (*cb)(Sprite *, void *), void *data);

为精灵注册鼠标事件回调。click 在精灵上点击时触发,down/up 在精灵上按下/释放鼠标时触发,hover 在鼠标悬停在精灵上时持续触发。销毁精灵时自动清理事件。

精灵颜色叠加说明

sprite_set_color 采用正片叠底(Multiply)混色模式,每次调用都从原始备份图(origin_image)重新克隆工作副本再执行混色,因此多次调用不会累积加深颜色

颜色参数 r/g/b 控制色调,a 控制强度(0=完全透明/无效果,255=完全叠加):

  • sprite_set_color(s, 255, 0, 0, 120) 叠加红色调
  • sprite_set_color(s, 255, 255, 255, 255) 恢复原始颜色

Sprite 结构体字段

Sprite 的公有字段可以直接读写:

sprite->x = 100;       /* 直接设坐标 */
sprite->y = 200;
sprite->visible = 0;   /* 隐藏 */
sprite->ox = 0;        /* 原点移到左上角 */
sprite->oy = 0;

内部字段(不应直接修改):

  • image — 当前绘制用的位图(工作副本)
  • origin_image — 原始图片备份,每次 set_color 从此重新克隆
  • is_owner1=销毁时释放全部资源,0=克隆体仅释放自己的工作副本