Ответ 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