在 m x n 矩阵中绘制矩形、圆形或任意多边形

Draw rectangles, circles or arbitrary polygons in a m x n matrix

我想模拟二维物体周围的流动。因此,我用 C 编写了一个程序,它使用 Navier-Stokes 方程来描述流体的运动。现在我到了我真正想要的不仅仅是在模拟域中放置一个矩形的地步。要绘制这样的矩形,我只需执行以下操作:

for(int i=start_x; i<end_x; i++)
    for(int j=start_y; j<end_y; j++)
        M[i][j] = 1; // barrier cell = 1

这样做我得到了一个漂亮的矩形。没有惊喜。但是,如果我想模拟围绕圆形、十字形、三角形、机翼轮廓或任何其他任意多边形的流动,应该采用什么方法?有没有一种简单的方法可以在大小为 m x n 的矩阵 M 中绘制此类 2D 对象?


我刚刚找到了一种简单的方法来绘制几乎任何我想要的形状。 @Nominal Animal 的回答启发了我找到这个解决方案。我只是使用 .png 文件并使用命令 convert picture.png picture.pgm(使用 Linux)将其转换为 .pgm 文件。在我的代码中,我只需要多几行:

FILE *pgmFile;
pgmFile = fopen("picture.pgm", "r");
for(int i=0; i<1024; i++){
    for(int j=0; j<1024; j++){
        int d = fgetc(pgmFile);
        if(d < 255){
            M[i][j] = 1; // barrier cell = 1
        }
    }
}
fclose(pgmFile);

这里我用的是1024 x 1024像素的图片。如果像素的值小于 255(不是白色),那么我将 M[i][j] 的像素设置为 1。这是我使用 Stack Overflow 徽标制作的结果(通量来自左侧):

速度图,Re = 20000(雷诺数)

可能有更有效的方法,但这里有一种方法。

使用要绘制的多边形的方程在 C 中定义一个函数。该函数被定义为它接受一个点坐标,并且 returns 是否该点位于多边形内。例如,对于一个圆,该函数可以接受点 (x,y)、圆心 (x0,y0)、半径 r 和 return (x-x0)^2 + (y-y0)^2 - r^2 < 0。设此函数为 f.

如果可能,请确定多边形的边界框矩形,否则,确定完全包围多边形的最小矩形。这将为您提供一个矩形矩阵。

现在,迭代矩形矩阵中的点。对于每个点,调用您之前定义的函数。如果 return 为真,则分配坐标 1,如果 return 为假,则分配坐标 0。这将构建多边形。

假设你想画一个圆,圆心为(x0,y0),半径为r,那么你可以使用:

int f(int i, int j, int x0, int y0, int r)
{
    return pow((i-x0),2) + pow((j-y0),2) - pow(r,2) < 0;        
}


for(int i = x0-r; i <= x0 + r; i++)
{
    for(int j = y0-r; j <= y0 + r; j++)
    {
        if(f(i,j,x0,y0,r))
        {
            M[i][j] = 1;
        }
        else
        {
            M[i][j] = 0;
        }
    }
}

手头的问题归结为 rasterisation (Wikipedia); and to scan line conversion (siggraph.org)。

siggraph.org 篇文章详细解释了如何绘制 straight lines, circles and ellipses, and convex and concave 多边形。

然而,这是一个已经解决了很多次的问题。虽然 OP 当然可以实现必要的图元(直线、椭圆、三角形、多边形),但还有一种更简单的方法。

我建议 OP 为其他系统实现一个简单的 NetPBM format reader for P5 (binary grayscale pixmap) format, and the netpbm tools (from netpbm package in Linux distributions and BSD variants; see the Netpbm home page)以将任何图像转换为 easy-to-read PGM (P5) 文件,其中每个像素对应于 OP 矩阵中的一个元素。

这样一来,就可以使用例如Inkscape 使用矢量图形绘制系统,将其栅格化为任意大小(例如导出为 PNG 图像),使用 netpbm 工具(pngtopnmanytopnm 转换为 PGM (P5) 格式,然后是 ppmtopgm), 读取文件。事实上,在 POSIX.1 系统中(除了 windows 之外几乎所有地方),可以使用 popen("anytopnm path-to-file | pnmtopng", "r") (或稍微复杂一点的双 fork() 管道解决方案)来读取任何PGM (P5) 格式的像素图图像。

或者,可以考虑使用例如ImageMagick 库可以读取任何格式的像素图图像(JPEG、GIF、PNG 等)。


就我个人而言,无论是作为开发人员还是用户(尽管请注意,我明确是 non-Windows 用户;十多年来没有使用过 Microsoft 产品),我更喜欢 netpbm 方法。该程序,例如 mysim,将使用例如/usr/lib/mysim/read-image shell 脚本(或 Windows 中的程序,可能是 macs;或者,如果已定义,则由 MYSIM_READ_IMAGE 环境变量定义的脚本或程序),以读取指定的图像在命令行上,以 PGM (P5) 格式发出。主程序将简单地读取助手的输出。

这样,如果用户需要对输入文件进行特殊处理,他们可以简单地复制现有脚本,根据自己的需要修改它,然后安装在自己的主目录下(或全局,甚至替换现有的,如果它被所有用户使用的话)。

程序可以使用popen()fork()+execv()来执行脚本,输入文件名作为command-line参数,并读取输出parent 构造初始矩阵的过程。

出于多种原因,我更喜欢这种方法而不是图像库方法。首先,它更加模块化,允许用户覆盖图像读取机制并在必要时对其进行操作。 (根据我的经验,这种覆盖并不经常需要,但是当它们需要时,它们非常有用,并且绝对值得。)其次,图像处理(在许多情况下非常复杂)是在一个单独的过程中完成的,这意味着在完全读取图像后,将释放读取和解密图像所需的所有内存(用于代码和数据)。第三,这种方法遵循 Unix philosophy and the KISS principle,后者在指导开发强大且有用的工具方面有着良好的记录。


这是一个示例程序,它从标准输入中读取二进制 PBM、PGM 或 PPM 文件(分别为 NetPBM P4、P5 和 P6 格式),并将其转换为矩阵结构,并用 01(基于从图像中读取的颜色或灰度值)。为了便于测试,程序将矩阵以PGM(P5)格式输出到标准输出。

该程序遵循 NetPBM 手册页中的格式规范(PBM (P4), PGM (P5), and PPM (P6) formats, respectively). The Wikipedia article on NetPBM formats 当前显示带有无效注释的示例(在 header 和数据之间)。NetPBM 手册页声明最终header 值后跟单个空白字符, 不是 注释。(如果注释可能跟在最终 header 值之后,则无法知道是否a # (binary 0x23 = 35) in the binary data start a comment, or is an actual data value.)

这是明确在 public 域中,或者等效地,根据 Creative Commons CC0 许可获得许可。这意味着你可以完全自由地以任何方式和任何你喜欢的方式使用下面的代码,即使是在商业项目中,但不能保证:如果它坏了,或者弄坏了什么,或者让你的头发着火了,你可以保留所有碎片,只能怪自己。

也就是说,它只经过了轻微测试,所以如果您发现其中的错误,请在评论中告诉我,以便我进行验证和修复。

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/* Matrix to read data into */
typedef struct {
    int            rows;
    int            cols;
    long           rowstride;
    long           colstride;
    unsigned char *data;        /* data[row*rowstride + col*colstride] */
} matrix;
#define MATRIX_INIT { 0, 0, 0, 0, NULL }

/* NetPBM (binary) formats supported */
#define PNM_PBM  4
#define PNM_PGM  5
#define PNM_PPM  6

/* Error codes from pnm_*() functions */
#define PNM_EOF       -1
#define PNM_INVALID   -2
#define PNM_OVERFLOW  -3


/* This helper function returns the NetPBM file identifier;
   PNM_PBM, PNM_PGM, PNM_PPM, or PNM_INVALID if unsupported.
*/
static int pnm_type(FILE *in)
{
    /* First character must be 'P'. */
    if (getc(in) != 'P')
        return PNM_INVALID;

    /* Second character determines the type. */
    switch (getc(in)) {
    case '4': return PNM_PBM;
    case '5': return PNM_PGM;
    case '6': return PNM_PPM;
    default:  return PNM_INVALID;
    }
}

/* This helper function reads a number from a NetPBM header,
   correctly handling comments. Since all numbers in NetPBM
   headers are nonnegative, this function returns negative
   when an error occurs:
    -1: Premature end of input
    -2: Value is too large (int overflow)
    -3: Invalid input (not a NetPBM format file)
*/
static int pnm_value(FILE *in)
{
    int  c;

    /* Skip leading whitespace and comments. */
    c = getc(in);
    while (c == '\t' || c == '\n' || c == '\v' ||
           c == '\f' || c == '\r' || c == ' ' || c == '#')
        if (c == '#') {
            while (c != EOF && c != '\n')
                c = getc(in);
        } else
            c = getc(in);

    if (c == EOF)
        return PNM_EOF;

    if (c >= '0' && c <= '9') {
        int value = 0;

        while (c >= '0' && c <= '9') {
            const int oldvalue = value;
            value = 10*value + (c - '0');
            if ((int)(value / 10) != oldvalue)
                return PNM_OVERFLOW;
            c = getc(in);
        }

        /* Do not consume the separator. */
        if (c != EOF)
            ungetc(c, in);

        /* Success. */
        return value;
    }

    return PNM_INVALID;
}

/* This helper function consumes the single newline
   following the final value in the header.
   Returns 0 if success, PNM_INVALID otherwise.
*/
static int pnm_newline(FILE *in)
{
    int c;

    c = getc(in);
    if (c == '\r')
        c = getc(in);
    if (c == '\n')
        return 0;

    return PNM_INVALID;
}

static void pnm_matrix_free(matrix *to)
{
    if (to) {
        free(to->data);
        to->rows = 0;
        to->cols = 0;
        to->rowstride = 0;
        to->colstride = 0;
        to->data = NULL;
    }
}

static int pnm_matrix_init(matrix *to, int rows, int cols)
{
    size_t  cells, bytes;

    if (rows < 1 || cols < 1)
        return PNM_INVALID;

    cells = (size_t)rows * (size_t)cols;
    if ((size_t)(cells / (size_t)rows) != (size_t)cols ||
        (size_t)(cells / (size_t)cols) != (size_t)rows)
        return PNM_OVERFLOW;

    bytes = cells * sizeof to->data[0];
    if ((size_t)(bytes / sizeof to->data[0]) != cells)
        return PNM_OVERFLOW;

    to->data = malloc(bytes);
    if (!to->data)
        return PNM_OVERFLOW;

    to->rows = rows;
    to->cols = cols;

    /* Default to a row-major data order. */
    to->colstride = 1L;
    to->rowstride = cols;

    return 0;
}

static int pnm_p4_matrix(FILE *in, matrix *to)
{
    int rows, cols, result, r, c, byte = 0;

    cols = pnm_value(in);
    if (cols < 1)
        return PNM_INVALID;

    rows = pnm_value(in);
    if (rows < 1)
        return PNM_INVALID;

    if (pnm_newline(in))
        return PNM_INVALID;

    result = pnm_matrix_init(to, rows, cols);
    if (result)
        return result;

    for (r = 0; r < rows; r++) {
        const long ri = r * to->rowstride;
        for (c = 0; c < cols; c++) {
            const long i = ri + c * to->colstride;

            switch (c & 7) {
            case 0:
                byte = getc(in);
                if (byte == EOF) {
                    pnm_matrix_free(to);
                    return PNM_INVALID;
                }
                to->data[i] = !!(byte & 128);
                break;
            case 1:
                to->data[i] = !!(byte & 64);
                break;
            case 2:
                to->data[i] = !!(byte & 32);
                break;
            case 3:
                to->data[i] = !!(byte & 16);
                break;
            case 4:
                to->data[i] = !!(byte & 8);
                break;
            case 5:
                to->data[i] = !!(byte & 4);
                break;
            case 6:
                to->data[i] = !!(byte & 2);
                break;
            case 7:
                to->data[i] = !!(byte & 1);
                break;
            }
        }
    }

    return 0;
}

static int pnm_p5_matrix(FILE *in, matrix *to)
{
    int rows, cols, max, r, c, result;

    cols = pnm_value(in);
    if (cols < 1)
        return PNM_INVALID;

    rows = pnm_value(in);
    if (rows < 1)
        return PNM_INVALID;

    max = pnm_value(in);
    if (max < 1 || max > 65535)
        return PNM_INVALID;

    if (pnm_newline(in))
        return PNM_INVALID;

    result = pnm_matrix_init(to, rows, cols);
    if (result)
        return result; 

    if (max < 256) {
        const int limit = (max + 1) / 2;
        int val;
        for (r = 0; r < rows; r++) {
            const long ri = r * to->rowstride;
            for (c = 0; c < cols; c++) {
                const long i = ri + c * to->colstride;

                val = getc(in);
                if (val == EOF) {
                    pnm_matrix_free(to);
                    return PNM_INVALID;
                }

                to->data[i] = (val < limit);
            }
        }
    } else {
        const int limit = (max + 1) / 2;
        int val, low;
        for (r = 0; r < rows; r++) {
            const long ri = r * to->rowstride;
            for (c = 0; c < cols; c++) {
                const long i = ri + c * to->colstride;

                val = getc(in);
                low = getc(in);
                if (val == EOF || low == EOF) {
                    pnm_matrix_free(to);
                    return PNM_INVALID;
                }
                val = 256*val + low;

                to->data[i] = (val < limit);
            }
        }
    }

    return 0;
}

static int pnm_p6_matrix(FILE *in, matrix *to)
{
    int rows, cols, max, r, c, result;

    cols = pnm_value(in);
    if (cols < 1)
        return PNM_INVALID;

    rows = pnm_value(in);
    if (rows < 1)
        return PNM_INVALID;

    max = pnm_value(in);
    if (max < 1 || max > 65535)
        return PNM_INVALID;

    if (pnm_newline(in))
        return PNM_INVALID;

    result = pnm_matrix_init(to, rows, cols);
    if (result)
        return result;

    if (max < 256) {
        const int limit = 128 * max;
        int       val, rval, gval, bval;

        for (r = 0; r < rows; r++) {
            const long ri = r * to->rowstride;
            for (c = 0; c < cols; c++) {
                const long i = ri + c * to->colstride;

                rval = getc(in);
                gval = getc(in);
                bval = getc(in);
                if (rval == EOF || gval == EOF || bval == EOF) {
                    pnm_matrix_free(to);
                    return PNM_INVALID;
                }

                val =  54 * rval
                    + 183 * gval
                    +  19 * bval;

                to->data[i] = (val < limit);
            }
        }
    } else {
        const int limit = 128 * max;
        int       val, rhi, rlo, ghi, glo, bhi, blo;

        for (r = 0; r < rows; r++) {
            const long ri = r * to->rowstride;
            for (c = 0; c < cols; c++) {
                const long i = ri + c * to->colstride;

                rhi = getc(in);
                rlo = getc(in);
                ghi = getc(in);
                glo = getc(in);
                bhi = getc(in);
                blo = getc(in);
                if (rhi == EOF || rlo == EOF ||
                    ghi == EOF || glo == EOF ||
                    bhi == EOF || blo == EOF) {
                    pnm_matrix_free(to);
                    return PNM_INVALID;
                }

                val =  54 * (rhi*256 + rlo)
                    + 183 * (ghi*256 + glo)
                    +  19 * (bhi*256 + blo);

                to->data[i] = (val < limit);
            }
        }
    }

    return 0;
}

int pnm_matrix(FILE *in, matrix *to)
{
    /* If the matrix is specified, initialize it. */
    if (to) {
        to->rows = 0L;
        to->cols = 0L;
        to->rowstride = 0L;
        to->colstride = 0L;
        to->data = NULL;
    }

    /* Sanity checks on parameters. */
    if (!to || !in || ferror(in))
        return PNM_INVALID;

    switch (pnm_type(in)) {
    case PNM_PBM: return pnm_p4_matrix(in, to);
    case PNM_PGM: return pnm_p5_matrix(in, to);
    case PNM_PPM: return pnm_p6_matrix(in, to);
    default:      return PNM_INVALID;
    }
}

int main(void)
{
    int r, c;
    matrix m = MATRIX_INIT;

    if (pnm_matrix(stdin, &m)) {
        fprintf(stderr, "Cannot parse standard input.\n");
        return EXIT_FAILURE;
    }

    fprintf(stderr, "Read %d rows, %d columns, from standard input.\n", m.rows, m.cols);

    /* For ease of debugging, we output the matrix as a PGM file. */
    printf("P5\n%d %d\n255\n", m.cols, m.rows);
    for (r = 0; r < m.rows; r++)
        for (c = 0; c < m.cols; c++)
            if (m.data[r * m.rowstride + c * m.colstride] == 0)
                putchar(255); /* White */
            else
                putchar(0);   /* Black */

    return EXIT_SUCCESS;
}

请注意,关于 OP 打算如何使用矩阵,我没有验证 bit/grayscale/color 转换是否是正确的方法。 (也就是说,"white" 或浅色是否应该在矩阵中产生 01。)如果您需要为 PBM 图像反转它,请改用 !(byte & NUMBER) .如果您需要为 PGM 或 PPM 图像反转它,请改用 (val >= limit)

该程序应该是有效的 C(甚至低至 C89),并且可以在任何体系结构上编译。在像 Windows 这样愚蠢的架构上,您可能必须 open/reopen "binary mode" 中的标准输入(在 fopen() 标志中包含 b),否则它们可能会损坏输入。

在 Linux,我用

编译并测试了程序 (example.c)
gcc -Wall -O2 example.c -o example
./example < inputfile.pbm > result-pbm.pgm
./example < inputfile.pgm > result-pgm.pgm
./example < inputfile.ppm > result-ppm.pgm

如果您希望能够绘制任意形状,您可能需要使用 SVG。我可以推荐nanosvg.h and nanosvgrast.h with an example (also uses stb_image for other image formats and xcb for displaying the image in X11) It's also available as at github gist here

#include <xcb/xcb.h>
#include <xcb/xcb_image.h>
#define STBI_NO_HDR
#define STBI_NO_LINEAR
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#define NANOSVG_IMPLEMENTATION
#include "nanosvg.h"
#define NANOSVGRAST_IMPLEMENTATION
#include "nanosvgrast.h"

int main(int argc, char **argv){
   xcb_connection_t *c = xcb_connect(0, 0);
   xcb_screen_t *s = xcb_setup_roots_iterator(xcb_get_setup(c)).data;
   int w, h, n,
      depth = s->root_depth,
      win_class = XCB_WINDOW_CLASS_INPUT_OUTPUT,
      format = XCB_IMAGE_FORMAT_Z_PIXMAP;
   xcb_colormap_t colormap = s->default_colormap;
   xcb_drawable_t win = xcb_generate_id(c);
   xcb_gcontext_t gc = xcb_generate_id(c);
   xcb_pixmap_t pixmap = xcb_generate_id(c);
   xcb_generic_event_t *ev;
   xcb_image_t *image;
   NSVGimage *shapes = NULL;
   NSVGrasterizer *rast = NULL;
   char *data = NULL;
   unsigned *dp;
   size_t i, len;
   uint32_t mask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK,
      value_mask = XCB_EVENT_MASK_EXPOSURE | XCB_EVENT_MASK_BUTTON_PRESS,
      values[] = { s->black_pixel, value_mask };

   if (argc<2) return -1;
   if ((data = stbi_load(argv[1], &w, &h, &n, 4)))
      ;
   else if ((shapes = nsvgParseFromFile(argv[1], "px", 96.0f))) {
      w = (int)shapes->width;
      h = (int)shapes->height;
      rast = nsvgCreateRasterizer();
      data = malloc(w*h*4);
      nsvgRasterize(rast, shapes, 0,0,1, data, w, h, w*4);
   }else return -1;
   for(i=0,len=w*h,dp=(unsigned *)data;i<len;i++) //rgba to bgra
      dp[i]=dp[i]&0xff00ff00|((dp[i]>>16)&0xFF)|((dp[i]<<16)&0xFF0000);
   xcb_create_window(c,depth,win,s->root,0,0,w,h,1,win_class,s->root_visual,mask,values);
   xcb_create_pixmap(c,depth,pixmap,win,w,h);
   xcb_create_gc(c,gc,pixmap,0,NULL);
   image = xcb_image_create_native(c,w,h,format,depth,data,w*h*4,data);
   xcb_image_put(c, pixmap, gc, image, 0, 0, 0);
   xcb_image_destroy(image);
   xcb_map_window(c, win);
   xcb_flush(c);
   while ((ev = xcb_wait_for_event(c))) {
      switch (ev->response_type & ~0x80){
      case XCB_EXPOSE: {
         xcb_expose_event_t *x = (xcb_expose_event_t *)ev;
         xcb_copy_area(c,pixmap,win,gc,x->x,x->y,x->x,x->y,x->width,x->height);
         xcb_flush(c);
      }break;
      case XCB_BUTTON_PRESS: goto end;
      default: break;
      }
   }
end:
   xcb_free_pixmap(c, pixmap);
   xcb_disconnect(c);
   return 0;
}

您可能需要修改光栅化器代码以适应您的特定格式而不是 X11,但您应该能够使用任何 svg 图像编辑器来生成您的形状(或者甚至只是使用视图框和路径手动编码它们) 例如,以黑白方式绘制图像,并仅使用 RGBA 结果中生成的 R、G 或 B 位中的任何一位,而不是将其转换为 X11 像素格式。

使用 svg 格式还允许您将其转换为任意图像格式(包括编辑中提到的那些),同时将其拉伸为任意大小,以便轻松查看拉伸 x 或 y 尺寸如何影响流量。 svg 格式甚至允许对单个形状进行大量转换以进行微调。

我比较喜欢用'sin'和'cos'来画圈。 如果你想要一些特殊的形状,比如椭圆形。您可以使用 'fac_specialX' 和 'fac_specialY' 使其不同。 如果'fac_specialX'和'fac_specialY'不是一个固定值(可能在循环中每次都改变),可以使形状更特别。(或者只尝试修改圆形数组的一部分)

int r=10;// radius
int x0=25,y0=25; // center
int circle_points = 300; // accuracy --> higher cause better quality but slow
int circleX[circle_points]; // circle array
int circleY[circle_points]; // circle array
// #define PI 3.1415926
double fac_angle =  ( 2*PI ) / circle_points;
// normal circle : fac_specialX & fac_specialY  set 1
// Oval : fac_specialX --> higher cause longer in X
//          fac_specialY --> higher cause longer in Y
double fac_specialX = 0.5;
double fac_specialY = 1.5;
// Calculate the coordinates
for(int i=0 ; i<circle_points ; i++) {

    // #include <math.h>  ->> sin cos
    circleX[i] = x0 + r*sin( (i+1)*fac_angle )*fac_specialX;
    circleY[i] = y0 + r*cos( (i+1)*fac_angle )*fac_specialY;
    // set the ponts in M array
    M[ circleY[i] ][ circleX[i] ] = 1;
}

如果图形数量不是很多(例如少于 100),您可以检查每个像素是否属于任何多边形。你只需要图形抽象:

/* Abstract struct for hloding figure (rectangle or elipse data)*/
typedef struct _figure_t* figure_t;

/* Pointer to pixel check algorithm implementation */
typedef int (*is_pixel_belongs_t)(uint32_t, uint32_t, figure_t);

struct _figure_t {
    is_pixel_belongs_t is_pixel_belongs;
};

/* figure implementation for rectangle */
typedef struct _rectangle_t {
    is_pixel_belongs_t is_pixel_belongs;
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
} * rectangle_t;

int is_pixel_belongs_rectangle(uint32_t x, uint32_t y, rectangle_t rect) {
    int x_belongs (x >= rect->x) && (x <= (rect->x + rect->width));
    int y_belongs (y >= rect->y) && (y <= (rect->y + rect->height));
    return x_belongs && y_belongs;
}

figure_t make_rect(uint32_t x, uint32_t y, uint32_t width, uint32_t height) {
    rectangle_t result = (rectangle_t) malloc(sizeof(struct _rectangle_t));
    result->is_pixel_belongs = (is_pixel_belongs_t) is_pixel_belongs_rectangle;
    result->x = x;
    result->y = x;
    result->width  = width;
    result->height = height;
}

/* figure implementation for elipse */
typedef struct _rectangle_t {
    is_pixel_belongs_t is_pixel_belongs;
    uint32_t x;
    uint32_t y;
    uint32_t width;
    uint32_t height;
} * rectangle_t;

/* Elipse implementation */
/* x^2/a^2 + y^2/b^2 = 1*/
figure_t make_elipse(uint32_t x, uint32_t y, uint32_t a, uint32_t b);


void main() {
    #define NUM_FIGURES 10
    figure_t figures[NUM_FIGURES] = {
        make_rect(0, 0, 40, 40),
        make_elipse(256, 128, 80, 40),
        /* Add more figures*/
    }

    /* Initialize your image */

    /* For each pixel */
    for(uint32_t x = 0; x < width; ++x) {
        for(uint32_t y = 0; y < height; ++x) {
            /* For each figure check if pixel (x,y) belongs to it*/
            for(uint32_t figure_ii = 0; figure_ii < NUM_FIGURES; ++figure_ii) {
                if (figures[figure_ii]->is_pixel_belongs(x, y)) {
                    image[x][y] = 1;
                    break;
                }
            }
        }
    }
}

这是一种非常简单的方法,与您所做的很接近。图形的内部循环可能会影响性能,如果您需要绘制 thousands/millions 任意图形,则需要使用辅助结构。一种选择是 binary space partitioning 方法。 IE。将你的图形组织成二叉树,这样你就可以在 O(log(n)) 时间内按像素找到一个图形,其中 n 是图形的数量。或者,您可以将图像吐到统一的网格中,并为每个图块保留数字列表。