我想模拟二维物体周围的流动。因此我在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徽标(通量来自左侧)的结果:
速度图,Re = 20000(雷诺数)
答案 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 lines,circles and ellipses以及convex和concave多边形的详细说明。
然而,这是一个已经解决了很多次的问题。虽然OP当然可以实现必要的基元(线,椭圆,三角形,多边形),但有一种更简单的方法。
我建议OP为P5(二进制灰度像素图)格式实现一个简单的NetPBM format读取器,在Linux发行版和BSD变体中实现netpbm tools(来自netpbm
包;请参阅{ {3}}用于其他系统)将任何图像转换为易于阅读的PGM(P5)文件,其中每个像素对应于OP矩阵中的一个元素。
这样,可以使用例如Inkscape使用矢量图形绘制系统,光栅化任意大小(例如导出为PNG图像),使用netpbm工具(pngtopnm
或anytopnm
转换为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_IMAGE
或popen()
+ fork()
执行脚本,输入文件名作为命令行参数,并读取父进程中的输出构建初始矩阵。
出于多种原因,我更喜欢这种方法而不是图像库方法。首先,它更模块化,允许用户覆盖图像读取机制并在必要时对其进行操作。 (根据我的经验,这些覆盖不是经常需要的,但是当它们存在时,它们非常有用,并且绝对值得。)其次,图像处理(在许多情况下非常复杂)是在一个单独的过程中完成的,这意味着当图像被完全读取时,释放读取和解密图像所需的所有存储器(用于代码和数据)。第三,这种方法遵循Netpbm home page和Unix philosophy,它们在指导开发强大而有用的工具方面有着良好的记录。
这是一个示例程序,它从标准输入读取二进制PBM,PGM或PPM文件(分别是NetPBM P4,P5和P6格式)到矩阵结构,用execv()
填充矩阵或0
(基于从图像中读取的颜色或灰度值)。为便于测试,程序将矩阵输出为PGM(P5)格式的标准输出。
该程序遵循NetPBM手册页中的格式规范(分别为KISS principle,PBM (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
是数字。或者,您可以将图像吐出到均匀网格,并保留每个图块的图表列表。