【问题标题】:Draw text in circle with Java AWT (with the letters oriented accordingly)使用 Java AWT 在圆圈中绘制文本(字母方向相应)
【发布时间】:2020-08-07 12:52:59
【问题描述】:

我正在尝试使用带有AffineTranform 的Java AWT 将给定​​的字符串绘制成一个圆圈,其中字母也会沿着圆圈倒置。

我从the following program 的代码开始单独绘制一条曲线。
我还使用a snippet I found here 的坐标计算来绘制模拟时钟的数字。

下面是我的代码。老实说,我不能 100% 理解这些方法如何工作以修复我的代码。在coordstheta 值的试错尝试中,我一直在摆弄。

import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.font.FontRenderContext;
import java.awt.font.GlyphVector;
import java.awt.geom.AffineTransform;
import java.awt.geom.Point2D;

public class Main extends Panel {
  public static void main(String[] args){
    Frame f = new Frame("Circle Text");
    f.add(new Main());
    f.setSize(750, 750);
    f.setVisible(true);
  }

  private int[] getPointXY(int dist, double rad){
    int[] coord = new int[2];
    coord[0] = (int) (dist * Math.cos(rad) + dist);
    coord[1] = (int) (-dist * Math.sin(rad) + dist);
    return coord;
  }

  @Override
  public void paint(Graphics g){
    Graphics2D g2 = (Graphics2D) g;

    g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
        RenderingHints.VALUE_ANTIALIAS_ON);

    // Hard-coded for now, using 12 characters for 30 degrees angles (like  a clock)
    String text = "0123456789AB";

    Font font = new Font("Serif", 0, 25);
    FontRenderContext frc = g2.getFontRenderContext();
    g2.translate(200, 200); // Starting position of the text

    GlyphVector gv = font.createGlyphVector(frc, text);
    int length = gv.getNumGlyphs(); // Same as text.length()
    final double toRad = Math.PI / 180;
    for(int i = 0; i < length; i++){
      //Point2D p = gv.getGlyphPosition(i);
      int[] coords = getPointXY(100, -360 / length * i * toRad + Math.PI / 2);
      double theta = 2 * Math.PI / (length + 1) * i;
      AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
      at.rotate(theta);
      Shape glyph = gv.getGlyphOutline(i);
      Shape transformedGlyph = at.createTransformedShape(glyph);
      g2.fill(transformedGlyph);
    }
  }
}

这是当前的输出:

我还注意到,如果我在 theta 公式中使用 (2 * length) 而不是 (length + 1),则字符串的前半部分似乎位于正确的位置,但角度不正确(字符 '6'侧向/旋转 90 度,而不是倒置/旋转 180 度):

正如我所提到的,我真的不知道AffineTransform 在给定坐标和θ 方面是如何工作的。对此的解释将不胜感激,如果有人可以帮助我修复代码,则更是如此。
另请注意,我想为可变长度的字符串实现此公式。我现在已将其硬编码为 "0123456789AB"(12 个字符,因此它类似于具有 30 度步长的时钟),但它也应该适用于假设为 8 个字符或 66 个字符的字符串。


编辑:根据 @MBo 的建议,我对代码进行了以下修改:

int r = 50;
int[] coords = getPointXY(r, -360 / length * i * toRad + Math.PI / 2);
gv.setGlyphPosition(i, new Point(coords[0], coords[1]));
final AffineTransform at = AffineTransform.getTranslateInstance(0, 0);
at.rotate(-2 * Math.PI * i / length);
at.translate(r * Math.cos(Math.PI / 2 - 2 * Math.PI * i / length),
             r * Math.sin(Math.PI / 2 - 2 * Math.PI * i / length));
Shape glyph = gv.getGlyphOutline(i);
Shape transformedGlyph = at.createTransformedShape(glyph);
g2.fill(transformedGlyph);

我现在确实有一个圈子,所以这就是问题,但不幸的是仍然存在三个问题:

  • 第一个字符的起始位置是 ~4 点钟而不是顶部。
  • 角色的顶部朝向圆心的角度不正确
  • 字符串是逆时针而不是顺时针绘制的

最后一个问题很容易解决,通过在旋转中将-2 更改为2

但是另外两个呢?


EDIT2:我误读了@MBo 关于初始字形集的答案的一小部分。它现在正在工作。与上面的编辑相比,这里生成的代码再次发生变化:

gv.setGlyphPosition(i, new Point(-length / 2, -length / 2));
AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
at.rotate(2 * Math.PI * i / length);

虽然我仍然看到较大输入字符串存在一些小问题,但我会对此进行调查。


EDIT3:已经有一段时间了,但我刚刚回到这个问题,我发现我很容易尝试的长度为 66 的测试用例的错误:360 应该是360d,因为360/length 会如果 360 不能被长度整除,则使用整数除法。
我现在有了这个,它可以按预期工作任何长度。请注意,中心并不完全正确,@Mbo 提供的答案可以提供帮助。我唯一的目标是制作文本圈(长度为 66)。它在屏幕上的哪个位置以及有多大并不那么重要。

int[] coords = this.getPointXY(r, -360.0 / length * i * toRad + Math.PI / 2);
  gv.setGlyphPosition(i, new Point(0, 0));
  AffineTransform at = AffineTransform.getTranslateInstance(coords[0], coords[1]);
  at.rotate(2 * Math.PI * i / length);
  at.translate(r * Math.cos(Math.PI / 2 - 2 * Math.PI * i / length),
      r * Math.sin(Math.PI / 2 - 2 * Math.PI * i / length));
  at.translate(-FONT_SIZE / 2, 0);

【问题讨论】:

    标签: java text geometry awt affinetransform


    【解决方案1】:

    您的初始角度是 Pi/2 用于位置,0 用于字形旋转。 要正确设置旋转和位置,我建议:

    • 将字形放在坐标原点 (0,0)
    • 旋转-2*Math.PI * i / length
    • 翻译成r*cos(Math.PI/2 - 2*Math.PI * i / length)r*sin(Math.PI/2 - 2*Math.PI * i / length)
    • 按圆心坐标翻译

    步骤:

    注意 - 旋转,然后移位。

    这种方法可能会产生良好但不完美的结果。为了更好看,您可以添加第一步 - 将字形翻译一半大小以提供围绕其中心的旋转。所以顺序:

    • 按 -glyphpixelsize/2 移动
    • 旋转
    • 移动到最终位置(相对于零,然后按圆心移动)

    【讨论】:

    • 所以我在getPointXY(100, theta)at.rotate(theta) 通话中都使用double theta = -360 / length * i * toRad + Math.PI / 2;?在这种情况下,我得到this as result。如何用这个 theta 计算字形旋转?另外,getPointXY 坐标中的dist 值是否会影响计算,还是只是调整大小?
    • 嗯,我的错。 Pi/2 仅用于位置。将详细说明。
    • 我还是不太明白。我现在确实有一个圆圈,但仍然有很多问题,比如不同的起始位置和逆时针旋转。我对我的帖子进行了编辑,说明了我如何解释您回答的最佳建议,将字形放在0,0,先旋转它,然后再翻译它。编辑:通过在旋转中将-2 更改为2,可以轻松修复逆时针方向。
    • 但你将字形放入coords 然后进行零位偏移,而它必须接近 0,0 !
    • 啊,当然!.. 现在我得到的结果确实几乎是正确的,所以我只需要应用你答案的第二部分来纠正轻微的变化。感谢到目前为止。如果我有任何其他问题,我会回复您。
    猜你喜欢
    • 1970-01-01
    • 2022-01-20
    • 2013-11-08
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    • 2021-10-15
    • 1970-01-01
    • 2014-04-11
    相关资源
    最近更新 更多