Сохранение постоянной ширины линии между преобразованиями - PullRequest
1 голос
/ 09 июля 2019

Я пытаюсь нарисовать некоторые базовые фигуры с применением преобразований перемещения, поворота и масштабирования.

Однако линии сужаются при уменьшении системы координат (и я хочу, чтобы она оставалась постоянной):

click

См. Прилагаемый блок кода ниже, который иллюстрирует то, что я уже пробовал.

Официальный учебник Cairo предоставляет такой способ обновления ширины линии:

double ux=1, uy=1;
cairo_device_to_user_distance (cr, &ux, &uy);
if (ux < uy)
    ux = uy;
cairo_set_line_width (cr, ux);

(см. https://cairographics.org/tutorial/, раздел «Советы и рекомендации» / «Ширина линии»)

import cairo

def rect(ctx, x, y, w, h):
    ctx.move_to(x, y)
    ctx.rel_line_to(w, 0)
    ctx.rel_line_to(0, h)
    ctx.rel_line_to(-w, 0)
    ctx.close_path()
    ctx.stroke()


surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 300, 300)
context = cairo.Context(surface)

context.scale(300, 300)

# background
context.set_source_rgb(0.5, 0.8, 0.8)
context.paint()

# new stroking color is black
context.set_source_rgb(0, 0, 0)

# translating to the center point
context.translate(0.5, 0.5)

# See https://cairographics.org/tutorial/ -> Tips and Tricks -> Line width
context.set_line_width(context.device_to_user_distance(1, 1)[0])

# So far so good, the rect is ok
rect(context, -0.1, -0.1, 0.2, 0.2)

# With each iteration, I rotate the coordinate system 60˚ clockwise, 
#  translate and scale down a bit
for i in range(6):
    scale_factor = 0.5
    angle = np.pi/3.
    dy = -0.3

    context.rotate(angle)
    context.translate(0, dy)
    context.scale(scale_factor, scale_factor)

    old_lw = context.get_line_width()
    dist = context.device_to_user_distance(1, 1)
    print('{0} Old: {1:2.4f}   New: {2:2.4f}'.format(i, old_lw, dist[0]))
    context.set_line_width(dist[0])

    rect(context, -0.05, -0.05, 0.1, 0.1)

    # Scaling and translating back, keeping current rotation cumulative
    context.scale(1/scale_factor, 1/scale_factor)
    context.translate(0, -dy)

surface.write_to_png('test.png')

Печатный блок выдает это:

0 Old: 0.0033   New: 0.0091
1 Old: 0.0091   New: 0.0024
2 Old: 0.0024   New: -0.0067
3 Old: 0.0000   New: -0.0091
4 Old: 0.0000   New: -0.0024
5 Old: 0.0000   New: 0.0067

Итак, я предполагаю, что проблема в этой строке:

dist = context.device_to_user_distance(1, 1)

Во время итерации он начинает давать отрицательные значения. Это приводит к странному и глючному результату: click.

Чтобы нарисовать исходное изображение, на которое я ссылался в начале, нужно просто закомментировать строки с настройкой ширины линии внутри цикла.

Где я ошибаюсь? Заранее спасибо.

1 Ответ

1 голос
/ 09 июля 2019

Обновление: причина, по которой device_to_user_distance возвращает отрицательные значения, до сих пор неизвестна, но вопросы на самом деле легко решаются.

Перед применением любого масштабирующего преобразования я обновляю ширину линии, основываясь на ее текущем значении, например:

scale_factor = 0.5
current_lw = context.get_line_width()

# so the new line width value is twice as thick as the old one
new_lw = current_lw/scale_factor

context.scale(scale_factor, scale_factor)
context.set_line_width(new_lw)

# do drawing

# restoring back scaling transform and line width
context.set_line_width(new_lw*scale_factor)
context.scale(1./scale_factor, 1./scale_factor)
...