Отрисовка и закраска многоугольников в dlib

Данный простой рецепт публикуется как один из исторических материалов, который является сейчас частью проекта R.I.P и поможет вам справится с отрисовкой многоугольников (и не только правильных), а также с задачей их закраски.

Код рисования и закраски прямоугольников использует идеи из гостевой статьи Тимура Гафарова про векторную графику и реализует несколько иной интерфейс: процедуры принимают на вход в качестве аргументов некоторую поверхность (в нашем случае, изображение на котором будем рисовать), цвет для рисования/окрашивания и массив координат вершин многоугольника (отдельно массив абсцисс и массив ординат). При этом код работает с любыми арифметическими типами, а также использует собственный код для отрисовки линии-отрезка (метод Брезенхэма, процедура реализации которого также принимает любые арифметические типы в качестве аргументов-координат).

Код реализации со всеми вспомогательными процедурами:

import dlib.image;

private 
{
	import std.meta : allSatisfy;
	import std.traits : isIntegral, isFloatingPoint, Unqual;
}

// все типы арифметические ?
template allArithmetic(T...)
	if (T.length >= 1)
{
	template isNumberType(T)
	{
		enum bool isNumberType = isIntegral!(Unqual!T) || isFloatingPoint!(Unqual!T);

	}

	enum bool allArithmetic = allSatisfy!(isNumberType, T);
}

// draw line with Bresenham method
void drawBresenhamLine(T, U, V, W)(SuperImage surface, Color4f color, T x1, U y1, V x2, W y2)
	if (allArithmetic!(T, U, V, W))
{
	import std.algorithm;
	import std.math;
	

	float a = cast(float) x1;
	float b = cast(float) y1;
	float c = cast(float) x2;
	float d = cast(float) y2;

	float dx = (x2 - x1 >= 0 ? 1 : -1);
	float dy = (y2 - y1 >= 0 ? 1 : -1);
	
	float lengthX = abs(x2 - x1);
	float lengthY= abs(y2 - y1);
	float length = max(lengthX, lengthY);
	
	if (length == 0)
	{
		surface[cast(int) x1, cast(int) y1] = color;
	}
	
	if (lengthY <= lengthX)
	{
		float x = x1;
		float y = y1;
		
		length++;
		while (length--)
		{
			surface[cast(int) x, cast(int) y] = color;
			x += dx;
			y += (dy * lengthY) / lengthX;
		}
	}
	else
	{
		float x = x1;
		float y = y1;
		
		length++;
		while(length--)
		{
			surface[cast(int) x, cast(int) y] = color;
			x += (dx * lengthX) / lengthY;
			y += dy;
		}
	}
}

// Точка внутри многоугольника ?
bool insidePolygon(S, T, U, V)(S pointX, T pointY, U[] polyX, V[] polyY)
	if (allArithmetic!(S, T, U, V))
{
	import std.algorithm;
	import std.conv;
	import std.range;

	assert(polyX.length == polyY.length);

    bool inside = false;

    auto px = cast(float) pointX;
    auto py = cast(float) pointY;

    auto poly_x = polyX.map!(a => to!float(a)).array;
    auto poly_y = polyY.map!(a => to!float(a)).array;

    size_t i = 0;
    size_t j = poly_x.length - 1;

    for (i = 0; i < poly_x.length; i++)
    {
        float ax = poly_x[i]; float ay = poly_y[i];
        float bx = poly_x[j]; float by = poly_y[j];

        if ((ay > py) != (by > py) && (px < (bx - ax) * (py - ay) / (by - ay) + ax))
            inside = !inside;
        j = i;
    }

    return inside;
}

// Нарисовать многоугольник
auto drawPolygon(S, T)(SuperImage surface, Color4f color, S[] polyX, T[] polyY)
	if (allArithmetic!(S, T))
{
	assert(polyX.length == polyY.length);

	import std.algorithm;
	import std.conv;
	import std.range;

	auto newSurface = surface.dup;

	auto poly_x = polyX.map!(a => to!float(a)).array;
	auto poly_y = polyY.map!(a => to!float(a)).array;

	auto startX = poly_x[0], startY = poly_y[0];

	foreach (i; 0..poly_x.length)
	{
		newSurface.drawBresenhamLine(color, startX, startY, poly_x[i], poly_y[i]);
		startX = poly_x[i];
		startY = poly_y[i];
	}

	newSurface.drawBresenhamLine(color, poly_x[0], poly_y[0], poly_x[$-1], poly_y[$-1]);

	return newSurface;
}

// Закрасить многоугольник
auto fillPolygon(S, T)(SuperImage surface, Color4f color, S[] polyX, T[] polyY)
	if (allArithmetic!(S, T))
{
	assert(polyX.length == polyY.length);

	auto newSurface = surface.dup;
	
    foreach (float x; 0..surface.width)
    {
    	foreach (float y; 0..surface.height)
    	{
    		if (insidePolygon(x, y, polyX, polyY))
    		{
    			newSurface[cast(int) x, cast(int) y] = color;
    		}
    	}
    }

    return newSurface;
}

Простейший эксперимент с закрашиванием:

void main()
{
	SuperImage surface = image(256, 256);
	Color4f color = Color4f(0.5f, 0.1f, 0.5f);
	float[] xs = [1, 20, 40, 50, 20, 10];
	float[] ys = [10, 20, 60, 70, 200, 150];

	surface
			.fillPolygon(color, xs, ys)
			.fillPolygon(color, [190, 250, 190, 250], [200, 250, 250, 200])
			.savePNG(`test.png`);
}

Результат:

Добавить комментарий