代码之家  ›  专栏  ›  技术社区  ›  Gilfoyle

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

  •  12
  • Gilfoyle  · 技术社区  · 7 年前

    我想模拟二维物体周围的流动。因此,我用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 ?


    我刚刚找到了一种简单的方法来画我想要的任何形状。“标称动物”的答案启发我找到了这个解决方案。我只是用一个 .png 文件并将其转换为 .pgm 使用命令的文件 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] enter image description here

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

    5 回复  |  直到 7 年前
        1
  •  7
  •   GoodDeeds    7 年前

    也许有更有效的方法可以做到这一点,但这里有一种方法。

    使用要绘制的多边形的方程在C中定义函数。该函数的定义使其接受点坐标,并返回点是否位于多边形内。例如,对于圆,函数可以接受点 (x,y) ,中心 (x0,y0) ,以及半径 r (x-x0)^2 + (y-y0)^2 - r^2 < 0 .让此函数为 f .

    现在,迭代矩形矩阵中的点。对于每个点,调用之前定义的函数。指定坐标a 1 如果返回True,并且 0

    假设你想画一个有中心的圆 (x0,y0) ,则可以使用:

    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;
            }
        }
    }
    
        2
  •  3
  •   Nominal Animal    7 年前

    rasterisation scan line conversion (siggraph.org)。

    straight lines , circles and ellipses convex concave 多边形。

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

    我建议OP实现一个简单的 NetPBM format P5(二进制灰度pixmap)格式的读取器,以及 netpbm tools (来自 netpbm Netpbm home page 对于其他系统),将任何图像转换为易于读取的PGM(P5)文件,其中每个像素对应于OP矩阵中的一个元素。

    这样,可以使用Inkscape等工具使用矢量图形绘制系统,将其光栅化为任意大小(例如导出为PNG图像),使用netpbm工具转换为PGM(P5)格式( pngtopnm anytopnm ppmtopgm ),并读取文件。事实上,在POSIX中。1系统(除了windows之外,几乎任何地方都可以使用) popen("anytopnm path-to-file | pnmtopng", "r") (或稍微复杂一点的两个- fork()

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


    就个人而言,无论是作为开发人员还是用户(尽管请注意,我显然是非Windows用户;十多年来没有使用过Microsoft产品),我更喜欢netpbm方法。比如说这个节目 mysim ,将使用例如。 /usr/lib/mysim/read-image shell脚本(或Windows中的程序,可能是Mac;或者,如果定义了,则是由 MYSIM_READ_IMAGE

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

    该程序可以使用 popen() 分叉() + execv() 以输入文件名作为命令行参数执行脚本,并在父进程中读取输出以构造初始矩阵。

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


    下面是一个示例程序,它将二进制PBM、PGM或PPM文件(分别为NetPBM P4、P5和P6格式)从标准输入读取到矩阵结构中,并用 0 1

    PBM (P4) , PGM (P5) PPM (P6) 格式)。维基百科文章 NetPBM formats 评论。(如果注释可能在最终标题值之后,则不可能知道 #

    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;
    }
    

    0 或者 在矩阵中。)如果需要将其反转为PBM图像,请使用 !(byte & NUMBER) (val >= limit)

    b fopen() 标志),否则可能会损坏输入。

    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
    
        3
  •  1
  •   Wei-Yuan Chen    7 年前

    如果你想要一些特殊的形状,比如椭圆形。您可以使用“fac\u specialX”和“fac/u specialY”使其有所不同。 如果“fac\u 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
  •   ivaigult    7 年前

    /* 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结构。一种选择是 binary space partitioning 方法一、 e.将您的图形组织到二叉树,以便您可以在中按像素查找图形 O(log(n)) n 是数字的数量。或者,你们可以把你们的图像吐到统一的网格上,并为每个瓷砖保留图形列表。

        5
  •  1
  •   technosaurus    7 年前

    如果希望能够绘制任意形状,可能需要使用SVG。我可以推荐 nanosvg.h and nanosvgrast.h 举个例子(也使用 stb_image 对于其他图像格式和用于在X11)中显示图像的xcb,它也可以在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图像编辑器来生成形状(甚至只是使用viewbox和路径手动编码)。例如,以黑白绘制图像,只需使用RGBA结果中生成的R、G或B位中的任何一位,而不是将其转换为X11像素格式。

    使用svg格式还可以将其转换为任何任意图像格式(包括编辑中提到的格式),同时将其拉伸到任何大小,从而很容易看到拉伸x或y维度如何影响流。svg格式甚至允许对单个形状进行大量转换以进行微调。