Подтвердить что ты не робот

Нарисуйте прямоугольники, круги или произвольные многоугольники в матрице m x n

Я хочу моделировать поток вокруг объектов в двух измерениях. Поэтому я написал программу на C, которая использует уравнения Навье-Стокса для описания движения жидкостей. Теперь я пришел к тому, что на самом деле хочу больше, чем просто разместить прямоугольник в домене моделирования. Чтобы нарисовать такой прямоугольник, я просто делаю что-то вроде:

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

Выполняя это, я получаю красивый прямоугольник. Не удивительно. Но какой будет подход, если я хочу моделировать поток вокруг круга, крест, треугольник, профиль крыла или любой другой произвольный многоугольник? Есть ли простой способ нарисовать такие 2D-объекты в матрице M размера m x n?


Я просто нашел простой способ рисовать практически любую форму, которую я хочу. Ответ @Nominal Animal вдохновил меня найти это решение. Я просто использую файл .png и конвертирую его в файл .pgm с помощью команды convert picture.png picture.pgm (используя Linux). В моем коде мне нужны только несколько строк:

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. Вот результат, который я сделал с логотипом (поток идет слева): введите описание изображения здесь

График скорости, Re = 20000 (число Рейнольдса)

4b9b3361

Ответ 1

Проблема под рукой сводится к rasterisation (Wikipedia); и в частности, преобразование линии сканирования (siggraph.org).

В статье siggraph.org содержатся подробные объяснения того, как рисовать прямые линии, круги и эллипсы и выпуклый и вогнутые полигоны.

Однако это проблема, которая уже решена много раз. Хотя OP, безусловно, может реализовать необходимые примитивы (линии, эллипсы, треугольники, многоугольники), существует гораздо более простой подход.

Я предлагаю, чтобы OP реализовал простой NetPBM формат для формата P5 (бинарный градаций серого) и netpbm tools (из пакета netpbm в дистрибутивах Linux и вариантах BSD, см. Домашняя страница Netpbm для других систем) для преобразования любого изображения в легко читаемый файл PGM (P5), где каждый пиксель соответствует одному элементу в матрице OP.

Таким образом, можно использовать, например, Inkscape для рисования системы с использованием векторной графики, растрировать ее на любом уровне (например, экспортировать как изображение PNG), конвертировать в формат PGM (P5) с помощью инструментов netpbm (pngtopnm или anytopnm, а затем ppmtopgm), и прочитать файл. Действительно, в системах POSIX.1 (почти везде, кроме окон) можно использовать popen("anytopnm path-to-file | pnmtopng", "r") (или чуть более сложное двух- fork() -трубное решение) для чтения любого изображения pixmap в формате PGM (P5).

В качестве альтернативы можно было бы использовать, например, библиотеку ImageMagick для чтения практически любых изображений в формате pixmap (JPEG, GIF, PNG и т.д.).


Лично, как разработчик, так и пользователь (хотя обратите внимание, что я явно не являюсь пользователем Windows, не использовал продукты Microsoft более десяти лет), я бы предпочел подход netpbm. Программа, скажем mysim, будет использовать, например, /usr/lib/mysim/read-image shell script (или программа в Windows, возможно, в macs или, если определено, script или программа, определяемая переменной среды MYSIM_READ_IMAGE), чтобы прочитать изображение, указанное в командной строке, PGM (P5). Основная программа просто прочитала бы выход хелпера.

Таким образом, если пользователю нужна специальная обработка входных файлов, они могут тривиально скопировать существующий script, изменить его в соответствии с их собственными потребностями и установить где-то в своем домашнем каталоге (или во всем мире, или даже заменить существующий, если он все равно используется всеми пользователями).

Программа может использовать либо popen(), либо fork() + execv() для выполнения script, с входным именем файла в качестве параметра командной строки и считыванием вывода в родительском процессе для построения исходной матрицы.

Я предпочитаю этот подход по подходу к библиотеке изображений по ряду причин. Во-первых, он более модульный, позволяя пользователю переопределять механизм считывания изображений и манипулировать им, если это необходимо. (По моему опыту, такие переопределения не очень часто нужны, но когда они есть, они чрезвычайно полезны и, безусловно, в целом их стоят.) Во-вторых, обработка изображений (которая во многих случаях довольно сложна) выполняется в отдельном процессе, что означает, что вся память (для кода и данных), необходимая для чтения и расшифровки изображения, выводится, когда изображение полностью считывается. В-третьих, этот подход следует философии Unix и Принцип KISS, которые имеют проверенный опыт руководства разработкой надежных и полезных инструментов.


Вот пример программы, которая считывает двоичный файл PBM, PGM или PPM (форматы NetPBM P4, P5 и P6 соответственно) со стандартного ввода в матричную структуру, заполняя матрицу 0 или 1 (на основе цветов или оттенков серого, считанных с изображения). Для удобства тестирования программа выводит матрицу на стандартный вывод в формате PGM (P5).

Программа следует спецификациям формата на страницах руководства NetPBM (для PBM (P4), PGM (P5) и PPM (P6), соответственно). Статья в Википедии о Форматы NetPBMв настоящее время показывают примеры с недопустимыми комментариями (между заголовком и данными). На страницах руководства NetPBM указано, что за последним значением заголовка следует одиночный символ пробела, а не комментарий. (Если комментарий может следовать за конечным значением заголовка, невозможно узнать, запускает ли символ # (двоичный 0x23 = 35) в двоичных данных комментарий или представляет собой фактическое значение данных.)

Это явно находится в общественном достоянии или, что эквивалентно, лицензируется в соответствии с лицензией 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 намеревается использовать матрицу. (То есть, "белые" или светлые цвета должны давать 0 или 1 в матрице.) Если вам нужно инвертировать его для изображений PBM, вместо этого используйте !(byte & NUMBER). Если вам нужно инвертировать его для изображений PGM или PPM, используйте (val >= limit).

Программа должна быть действительной C (вплоть до C89) и скомпилировать любую архитектуру. В таких глупых архитектурах, как Windows, вам может потребоваться открыть/снова открыть стандартный ввод в "двоичном режиме" (включая b в флагах fopen()), поскольку они в противном случае могут помешать вводу.

В 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

Ответ 2

Там могут быть более эффективные способы сделать это, но здесь одним способом.

Определите функцию в C, используя уравнение многоугольника, которое вы хотите нарисовать. Функция определена так, что она принимает координаты точки и возвращает, находится ли точка внутри многоугольника или нет. Например, для круга функция может принимать точку (x,y), центр (x0,y0) и радиус r и возвращать (x-x0)^2 + (y-y0)^2 - r^2 < 0. Пусть эта функция f.

Определите прямоугольник прямоугольной рамки многоугольника, если это возможно, или же самый наименьший прямоугольник, который вы можете определить, который полностью перекрывает многоугольник. Это даст вам прямоугольную матрицу.

Теперь перебираем точки в прямоугольной матрице. Для каждой точки вызовите функцию, которую вы ранее определили. Назначьте координату a 1, если она вернет True, и 0, если она вернет False. Это построит многоугольник.

Предположим, вы хотите нарисовать круг с центром (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;
        }
    }
}

Ответ 3

Если вы хотите иметь возможность рисовать произвольные фигуры, вы, вероятно, захотите использовать SVG. Я могу порекомендовать nanosvg.h и nanosvgrast.h с примером (также использует stb_image для других форматов изображений и xcb для отображения изображения в X11) Он также доступен как в github gist здесь

#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 для создания ваших фигур (или даже просто использовать их код с помощью окна просмотра и пути). экземпляр, нарисуйте свои изображения в черно-белом режиме и просто используйте любой из сгенерированных бит R, G или B в RGBA-результате вместо преобразования его в формат X11-пикселя.

Использование svg-формата также позволит вам преобразовать его в любой произвольный формат изображения (включая те, что упоминаются в редактировании), растягивая его до любого размера, что позволяет легко увидеть, как растягивание размеров x или y влияет на поток. Формат svg позволяет даже большое количество преобразований на отдельные фигуры для точной настройки.

Ответ 4

Я предпочитаю использовать "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;
}

Ответ 5

Если количество цифр не так велико (например, менее 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;
                }
            }
        }
    }
}

Это довольно простой подход и близко к тому, что вы сделали. Внутренний цикл по цифрам может повлиять на производительность, если вам нужно привлечь тысячи/миллионы произвольных цифр, вам нужно будет использовать aux-структуры. Одним из вариантов является подход двоичного разделения пространства. То есть упорядочивайте фигуры в двоичном дереве, чтобы вы могли найти цифру по пикселям в O(log(n)) время, когда n - количество цифр. Или вы можете направить свое изображение на единую сетку и сохранить список фигур для каждой плитки.