在Pycairo中将文本绕其中心旋转

11

社区。

我知道这里有很多答案、手册、教程和参考资料,还有更多关于这个问题的互联网资源。同时我也知道需要了解线性代数的知识。

但是,一想到要花时间理解所有理论和解决实践练习,我的头就要爆炸了,我甚至不能做最简单的事情 :(

如果您知道一个快速解决方案如何在呈现文本之前使其围绕其中心旋转,请告诉我,拜托了。

目前我有:

#...
cr.move_to(*text_center)
myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)

cr.save()
cr.translate(myX, myY)
cr.rotate(radians(text_angle))
cr.show_text(letter)
cr.restore()
#...

但我的字母并没有围绕自己旋转。它就像向右边倒下:( 我知道我的代码不对。也许我错过了转换,但我不知道如何使它正确。

更新:不幸的是,翻译对文本没有影响。

cr.translate(10000, 10000)
cr.rotate(radians(15))
cr.show_text("hello")

将完全相同

cr.rotate(radians(15))
cr.show_text("hello")

我不知道如何使文本绕其中心旋转,而不需要创建新的表面或类似于图形处理器中的新层 :(

4个回答

14

至少在我机器上可用的cairo版本(1.8.8),以下方法适用于我:

def text(ctx, string, pos, theta = 0.0, face = 'Georgia', font_size = 18):
    ctx.save()

    # build up an appropriate font
    ctx.select_font_face(face , cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    ctx.set_font_size(font_size)
    fascent, fdescent, fheight, fxadvance, fyadvance = ctx.font_extents()
    x_off, y_off, tw, th = ctx.text_extents(string)[:4]
    nx = -tw/2.0
    ny = fheight/2

    ctx.translate(pos[0], pos[1])
    ctx.rotate(theta)
    ctx.translate(nx, ny)
    ctx.move_to(0,0)
    ctx.show_text(string)
    ctx.restore()

可以按以下方式使用:

width = 500
height = 500
surface = cairo.ImageSurface(cairo.FORMAT_RGB24, width, height)
ctx = cairo.Context(surface)
ctx.set_source_rgb(1,1,1)
rect(ctx, (0,0), (width, height), stroke=False)
ctx.set_source_rgb(0,0,0)
for i in xrange(5):
    for j in xrange(5):
        x = 100 * i + 20
        y = 100 * j + 20
        theta = math.pi*0.25*(5*i+j)
        text(ctx, 'hello world', (x, y), theta, font_size=15)
surface.write_to_png('text-demo.png')

text-demo.png


天才和完美!哦,自从我问了那个问题,我就停止了我的项目,一直打开数学书籍。我知道你的解决方案是我想要的,如果你不介意的话,我会简单地将它粘贴到我的代码中,并在注释中写上你的名字。但数学很棒,很有趣,所以我会继续学习新的知识。您能告诉我在数学书籍中找到您的解决方案的位置:它的名称是什么?谢谢您的帮助。 - Крайст
1
@Schollii是正确的,但事实证明,Cairo API有一些复杂之处,使得这个过程变得有点有趣。关键点在于旋转总是发生在原点周围。这意味着您需要将原点平移至您想要绘制文本的位置。然后您可以进行旋转。最后,为了确保渲染的文本通过您的点,您需要进行最后一次平移(在旋转坐标系中)。move_to命令确保文本将从原点开始渲染。 - Andrew Walker
1
这种数学可以用“变换矩阵”一词来描述,它实际上只是一种表示坐标变换的方法。从非常广义的意义上讲,这是一个线性代数问题。在力学中,这个任务尤为重要(而力学对于从事物理学、机器人技术和游戏开发的软件开发人员来说也很重要)。 - Andrew Walker
当然,Schollii是正确的,但Python中的三角函数会降低精度很多。乍一看,您的方法更加精确,也许更快。我将在Web应用程序中使用此代码,这对于动态图像渲染和浮点运算非常重要。再次感谢您。线性代数就在我的路上 :) - Крайст
似乎对我没有起作用。我的特殊情况可能是我只使用单个字符。有什么想法吗? - blissweb

6

好的,cairo允许文本进行move_to和rotate操作。这意味着您需要找到move_to(T)的(x,y)值,使得当您旋转(R)时,文本的中心点位于所需位置c =(cx,cy):

enter image description here

因此,您必须解决方程Mv = c,其中v是文本中心相对于文本原点的位置:

M = T*R

T = (1 0 x)
    (0 1 y)
    (0 0 1)

R =  (cos r    -sin r   0)
     (sin r     cos r   0)
     (0            0    1)

v = (w/2, h', 1)

c = (cx, cy, 1)

h' = h/2 - (h - y_bearing)

检查:

  • 当 r=0(无旋转)时,您将得到 x=cx-w/2,y=cy-h' ,这是正确的答案
  • 当 r=-90(文本侧向,"向右"为"上")时,您将得到您期望的结果,即 x=cx-h',y=cy+w/2

对于Python代码,您需要重写上述方程,以便最终得到 A*t=b,其中 t=(x,y),然后计算 t=inv(A)*b。然后,您只需执行以下操作:

cr.move_to(x, y)
cr.rotate(r)
cr.show_text(yourtext)

请注意,cairo中的坐标系具有向下的+y,因此可能需要修正一些符号,而且y_bearing可能不正确,但是您已经理解了。

“将文本放置在文本中心” - 我该如何实现?我只有分离的函数 cr.move_to(x, y) 和分离的 cr.show_text(text)。我应该在翻译和旋转之前还是之后以及放置文本之前使用 move_to() 函数? - Крайст
我的 text_center 意味着原点的 x、y 坐标,以便将文本的中心点 (w/2, h/2) 与首选点完全重叠。 - Крайст
这些代码不会工作,因为旋转总是发生在current_point周围。如果我写:cr.move_to(a, b); cr.rotate(rad); cr.show_text(); - 它只会围绕(a, b)旋转,平移不会影响旋转。但我知道有一些公式,比如这里,可以计算新的坐标移动到哪里。 - Крайст
非常感谢您详细的回答。虽然我有几个问题不太理解,但我会尝试自己弄清楚,因为对我来说理解所有这些内容非常重要。 非常感谢! - Крайст
@Крайст 如果你能让所有人知道这是否是答案(标记为答案),或者至少有用(投票并评论你的发现),那就太好了。 - Oliver
请问,您能为新手解释一下您的答案吗?我只能感觉到它是正确的答案或非常有帮助,但我的知识不足以理解它 :( - Крайст

2

基于上述输入的类函数,支持多行文本。

def text(self, text, x, y, rotation=0, fontName="Arial", fontSize=10, verticalPadding=0):

    rotation = rotation * math.pi / 180

    self.ctx.select_font_face(fontName, cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
    self.ctx.set_font_size(fontSize)

    fascent, fdescent, fheight, fxadvance, fyadvance = self.ctx.font_extents()

    self.ctx.save()
    self.ctx.translate(x, y)
    self.ctx.rotate(rotation)

    lines = text.split("\n")

    for i in xrange(len(lines)):
        line = lines[i]
        xoff, yoff, textWidth, textHeight = self.ctx.text_extents(line)[:4]

        offx = -textWidth / 2.0
        offy = (fheight / 2.0) + (fheight + verticalPadding) * i

        self.ctx.move_to(offx, offy)
        self.ctx.show_text(line)

    self.ctx.restore()

过去的五年里有什么变化吗?这是我的宠物项目,我很久以前就放弃了它,开始重新学习数学。自那以后我没有回到这个项目。然而,我很想再次尝试一切。 - Крайст
12 变成了 17。 - nvd
我指的是Cairo/Pycairo。 - Крайст

1

应该

myX, myY = text_center[0] + (height / 2), text_center[1] - (width / 2)

myX, myY = text_center[0] - (width / 2), text_center[1] + (height / 2)

?

这可能解释了为什么它会向右下方倾斜


哦,请原谅,这是我的错。我在手机上凭记忆编写了这段代码。实际的代码与您的示例非常相似。但是当我回家后我会检查一下。您认为翻译是我唯一需要做的转换吗? - Крайст
这不是错误。结果是一样的。也许一些复杂的东西可以解决这种情况?也许新的转换矩阵或不同的代码顺序? - Крайст

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接