在m×n矩阵中绘制矩形,圆形或任意多边形

时间:2017-07-01 16:45:49

标签: c matrix drawing draw

我想模拟二维物体周围的流动。因此我在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的矩阵m x n中绘制这样的2D对象?

我刚刚找到了一种简单的方法来绘制我想要的几乎任何形状。 @Nominal Animal的答案激励我找到这个解决方案。我只使用.png文件并使用命令.pgm(使用Linux)将其转换为convert picture.png picture.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徽标(通量来自左侧)的结果: enter image description here

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

5 个答案:

答案 0 :(得分:7)

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

使用您想要绘制的多边形的等式在C中定义一个函数。定义该函数使得它接受点坐标,并返回该点是否位于多边形内部。例如,对于圆形,该函数可以接受点(x,y),中心(x0,y0)和半径r,并返回(x-x0)^2 + (y-y0)^2 - r^2 < 0。让这个函数为f

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

现在,迭代矩形矩阵中的点。对于每个点,调用先前定义的函数。如果坐标返回True,则指定坐标a 1;如果返回False,则指定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;
        }
    }
}

答案 1 :(得分:3)

手头的问题归结为rasterisation(维基百科);特别是scan line conversion(siggraph.org)。

siggraph.org文章包含有关如何绘制straight linescircles and ellipses以及convexconcave多边形的详细说明。

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

我建议OP为P5(二进制灰度像素图)格式实现一个简单的NetPBM format读取器,在Linux发行版和BSD变体中实现netpbm tools(来自netpbm包;请参阅{ {3}}用于其他系统)将任何图像转换为易于阅读的PGM(P5)文件,其中每个像素对应于OP矩阵中的一个元素。

这样,可以使用例如Inkscape使用矢量图形绘制系统,光栅化任意大小(例如导出为PNG图像),使用netpbm工具(pngtopnmanytopnm转换为PGM(P5)格式,然后{ {1}}),并阅读该文件。实际上,在POSIX.1系统中(除了窗口之外的任何地方),可以使用ppmtopgm(或稍微复杂的两个 - popen("anytopnm path-to-file | pnmtopng", "r")管道解决方案)以PGM(P5)格式读取任何像素图图像

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

就个人而言,无论是作为开发人员还是用户(虽然请注意我明确表示非Windows用户;未使用过十多年的微软产品),我更倾向于使用netpbm方法。该程序,例如fork(),将使用例如mysim shell脚本(或Windows中的程序,可能是mac;或者,如果已定义,由/usr/lib/mysim/read-image环境变量定义的脚本或程序),用于读取命令行上指定的映像,并将其发送到PGM(P5)格式。主程序只会读取助手的输出。

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

程序可以使用MYSIM_READ_IMAGEpopen() + fork()执行脚本,输入文件名作为命令行参数,并读取父进程中的输出构建初始矩阵。

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

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

该程序遵循NetPBM手册页中的格式规范(分别为KISS principlePBM (P4)PGM (P5)格式。关于PPM (P6)的维基百科文章目前显示带有无效注释的示例(标题和数据之间)。 NetPBM手册页指出最终标题值后跟一个空白字符,注释。 (如果注释可能跟在最终标题值之后,则无法知道二进制数据中的1(二进制0x23 = 35)是否开始注释,或者是实际数据值。)

这明确属于公共领域,或者等效地根据NetPBM formats许可进行许可。这意味着您可以完全自由地在任何地方和任何地方使用以下代码,即使是在商业项目中,但是没有任何保证:如果它破裂,或者破坏某些东西,或者让您的头发着火,您可以保留所有这些碎片只怪自己。

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

#

请注意,我没有根据OP打算如何使用矩阵来验证位/灰度/颜色转换是否正确。 (也就是说,&#34; white&#34;或浅色应该在矩阵中产生#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; } 0。)如果您需要将其反转为PBM图像,请使用{{1相反。如果您需要将其反转为PGM或PPM图像,请改用1

程序应该是有效的C(甚至低至C89),并在任何架构上编译。在像Windows这样的愚蠢体系结构上,您可能必须在&#34;二进制模式&#34;中打开/重新打开标准输入。 (在!(byte & NUMBER)标志中包含(val >= limit)),否则可能会破坏输入。

在Linux上,我使用

编译并测试了程序(b
fopen()

答案 2 :(得分:1)

如果您希望能够绘制任意形状,您可能想要使用SVG。我可以推荐nanosvg.h and nanosvgrast.h作为示例(也使用stb_image表示其他图像格式,使用xcb表示在X11中显示图像)它也可以在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格式甚至允许对各个形状进行大量转换以进行微调。

答案 3 :(得分:1)

我更喜欢使用'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;
}

答案 4 :(得分: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;
                }
            }
        }
    }
}

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