PyCairo: как изменить размер и повернуть изображение по его центру до окончательного холста

Используя PyCairo, я хочу иметь метод, который может помещать, изменять размер и поворачивать заданную ImageSurface в контексте, но вращаться по центру изображения (а не в левом верхнем углу)

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

У меня есть финальная поверхность ImageSurface (скажем, A), на которой написаны некоторые другие изображения и тексты. Я хочу поместить на него еще одну ImageSurface (скажем, B) в указанной позиции, где эта позиция является левым верхним, где поместить B на A. Затем мне нужно изменить размер B (уменьшить его размер) и повернуть его по центру, а не по верхнему левому углу.

Вот иллюстрация желаемого результата: введите здесь описание изображения

Я пробовал следующее, но безуспешно:

    def draw_rotated_image(ctx, image_surface, left, top, width, height, angle):
        ctx.save()
        w = image_surface.get_width()
        h = image_surface.get_height()
        cl = left / (width/w)
        ct = top  / (height/h)
        ctx.rotate(angle*3.1415927/180)
        ctx.scale(width/w, height/h)
        ctx.translate(cl + (-0.5*w),ct + (-0.5*h) )
        ctx.set_source_surface(image_surface, 0, 0)
        ctx.paint()

        ctx.restore()
        return

Большое спасибо за твою помощь :)


person CutMaster Prod    schedule 31.08.2020    source источник


Ответы (1)


Ну наконец-то я сделал это! (спасибо моему 14-летнему сыну, который заставил меня пересмотреть мою тригонометрию)

Я пытаюсь объяснить здесь свое решение. Во-первых, я НЕ МАТЕМАТИК. Так что, вероятно, есть лучший способ, и, конечно же, в моем объяснении есть ошибки, но я просто объясняю логический способ, которым я воспользовался для получения хорошего результата.

Лучшая идея для этого - сначала нарисовать круг вокруг прямоугольника, потому что нам нужно переместить верхний левый угол этого прямоугольника вокруг его собственного круга в соответствии с желаемым углом. Итак, чтобы получить радиус прямоугольного круга, нам нужно вычислить его гипотенузу, а затем разделить на 2:

hypothenuse = math.hypot(layerWidth,layerHeight)
radius = hypothenuse / 2

Тогда мы сможем нарисовать круг вокруг прямоугольника.

Во-вторых, нам нужно знать, под каким углом на этом круге находится фактический верхний левый угол прямоугольника. Итак, для этого нам нужно вычислить арктангенс прямоугольника, который равен arc-tan (высота/ширина). Но поскольку мы хотим знать, на сколько градусов мы далеки от 0°, нам нужно вычислить противоположное значение, так что arc-tan(width/height).

Наконец, еще одна особенность заключается в том, что Каир 0° на самом деле находится под углом 90°, так что нам придется снова повернуться.

Это можно показать на этом простом графике: введите здесь описание изображения

Итак, наконец, что необходимо понять? Если вы хотите нарисовать слой с углом, повернутым вокруг его центра, верхняя левая точка будет перемещаться по кругу в соответствии с желаемым углом. Верхнее левое положение с заданным углом 0 должно быть эталоном.

Итак, нам нужно получить новую позицию X-Y, где начать размещать слой, чтобы иметь возможность его вращать: введите здесь описание изображения

Теперь мы можем написать функцию, которая будет возвращать позицию X-Y верхнего левого прямоугольника, где его можно нарисовать под заданным углом:

def getTopLeftForRectangleAtAngle(layerLeft,layerTop,layerWidth,layerHeight,angleInDegrees):
    # now we need to know the angle of the top-left corner
    # for that, we need to compute the arc tangent of the triangle-rectangle:
    layerAngleRad = math.atan((layerWidth / layerHeight))
    layerAngle = math.degrees(layerAngleRad)

    # 0° is 3 o'clock. So we need to rotate left to 90° first
    # Then we want that 0° will be the top left corner which is "layerAngle" far from 0
    if (angleInDegrees >= (90 + layerAngle)):
        angleInDegrees -= (90 + layerAngle)
    else:
        angleInDegrees = 360 - ((90 + layerAngle) - angleInDegrees)
    
    angle = (angleInDegrees * math.pi / 180.0)

    centerLeft = layerLeft + (layerWidth / 2)
    centerTop  = layerTop  + (layerHeight / 2)

    # hypothenuse will help us knowing the circle radius
    hypothenuse = math.hypot(layerWidth,layerHeight)
    radius = hypothenuse / 2

    pointX = centerLeft + radius * math.cos(angle)
    pointY = centerTop  + radius * math.sin(angle)

    return (pointX,pointY)

И, наконец, вот как использовать его с изображением, которое мы хотим изменить, повернуть и написать в контексте:

def draw_rotated_image(ctx, image_surface, left, top, width, height, angle=0.0, alpha=1.0):
    ctx.save()
    w = image_surface.get_width()
    h = image_surface.get_height()

    # get the new top-left position according to the given angle
    newTopLeft = getTopLeftForRectangleAtAngle(left, top, width, height, angle)

    # translate
    ctx.translate(newTopLeft[0], newTopLeft[1])
    # rotate
    ctx.rotate(angle * math.pi / 180)
    # scale & write
    ctx.scale(width/w, height/h)
    ctx.set_source_surface(image_surface, 0, 0)
    ctx.paint_with_alpha(alpha)

    ctx.restore()
    return
person CutMaster Prod    schedule 04.09.2020