Шрифт в Java AWT отображается неправильно, если он больше 100 точек

Я пытаюсь преобразовать текст в изображение с помощью JAVA AWT. Когда я использую шрифт меньше 100 пунктов, все работает как положено. Но когда я использую, например, 240 точек, то некоторые части текста, которые перекрываются, становятся прозрачными, как на этом изображении . В примере изображения я использовал этот шрифт. Я без проблем использовал этот шрифт в текстовом редакторе.

Шрифт загружается с помощью этого кода:

Font font = Font.createFont(Font.TRUETYPE_FONT, new File("src/main/resources/Autumn in November.ttf"));
Font derived = font.deriveFont(240f);

Чтобы отобразить текст на изображении, я использую этот код:

 public static void saveTextToImage(String text, Font font) throws IOException {

        # Temp image to get the text dimensions
        BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
        Graphics2D tempGraphics = tempImage.createGraphics();
        tempGraphics.setFont(font);
        FontMetrics fontMetrics = tempGraphics.getFontMetrics();
        FontRenderContext fontRenderContext = tempGraphics.getFontRenderContext();
        GlyphVector gv = font.createGlyphVector(fontRenderContext, text);
        Area area = new Area(gv.getOutline());
        Rectangle2D rectangle2D = area.getBounds2D();
        int width = (int) rectangle2D.getWidth();
        int height = (int) rectangle2D.getHeight();

        // Create a new image with white background
        BufferedImage img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D graphics = img.createGraphics();
        graphics.setColor(Color.WHITE);
        graphics.fillRect(0, 0, width, height);

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

        // Draw the text on the image
        graphics.setColor(Color.BLACK);
        graphics.setFont(font);
        graphics.drawString(text, 10, fontMetrics.getAscent() + 5);

        // Save the image
        File outputFile = new File(text + ".jpg");
        ImageIO.write(img, "jpg", outputFile);
}

Я пытался изменить тип шрифта на OTF, добавлять и изменять RenderingHints, а также переключать тип изображения на PNG, но безуспешно. Я не знаю, что еще я мог бы попробовать.

Какая версия Явы? И зачем эти ненужные обходы? gv.getOutline() возвращает Shape, так что вы можете вызвать getBounds2D() напрямую, не копируя его в Area, но вы даже можете вызвать gv.getVisualBounds() в первую очередь. Ну, на самом деле, вы можете запросить размеры из fontMetrics, не создавая GlyphVector. Оставляя в стороне обходные пути, вы должны рассчитывать размеры, используя те же подсказки рендеринга, что и при рисовании. И когда вы впоследствии добавляете к координатам такие константы, как 10 и 5, вам нужно соответствующим образом настроить размер изображения.

Holger 09.05.2023 16:56

Один из вариантов — создать изображение с огромным текстовым шрифтом и использовать его в проекте Java Swing.

Gilbert Le Blanc 09.05.2023 16:56

@Holger Я пробовал 8, 11 и 17 с тем же результатом. Я согласен с обходом, но с помощью GlyphVector я могу получить более точный размер текста. Подсказки рендеринга не решают проблему с перекрытием.

awarus 09.05.2023 17:08

@GilbertLeBlanc Мне нужно большое количество этих изображений, создавать их вручную невозможно.

awarus 09.05.2023 17:10

Похоже на ошибку в отрисовке глифа, использование неправильного правила намотки ("четно-нечетное" вместо "ненулевое"), см. Википедия: ненулевое правило, особенно рисунок справа.

Harald K 10.05.2023 10:12
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
50
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это действительно странное поведение. Я не могу сказать вам, почему это происходит, но я могу предложить вам обходной путь; проблема не возникает при рендеринге глифов строки один за другим:

static final boolean USE_WORKAROUND = true;

public static void saveTextToImage(String text, Font font) throws IOException {
    // Temp image to get the GlyphVector and text dimensions
    BufferedImage tempImage = new BufferedImage(1, 1, BufferedImage.TYPE_INT_RGB);
    Graphics2D tempGraphics = tempImage.createGraphics();
    tempGraphics.setFont(font);
    tempGraphics.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    FontRenderContext fontRenderContext = tempGraphics.getFontRenderContext();
    GlyphVector gv = font.createGlyphVector(fontRenderContext, text);
    Rectangle2D rectangle2D = gv.getVisualBounds();
    int width = (int)(rectangle2D.getWidth());
    int height = (int)(rectangle2D.getHeight());

    // Create a new image with white background
    BufferedImage img
        = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    Graphics2D graphics = img.createGraphics();
    graphics.setBackground(Color.WHITE);
    graphics.clearRect(0, 0, width, height);
    graphics.setRenderingHint(
        RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    graphics.setColor(Color.BLACK);

    // using GlyphVector avoids repeating the work already done above
    if (!USE_WORKAROUND) {
        graphics.drawGlyphVector(gv,
            (float)-rectangle2D.getX(), (float)-rectangle2D.getY());
    } else {
        graphics.translate(-rectangle2D.getX(), -rectangle2D.getY());
        for(int gix = 0, gnum = gv.getNumGlyphs(); gix < gnum; gix++) {
            graphics.fill(gv.getGlyphOutline(gix));
        }
    }

    // Save the image
    File outputFile = new File(text + ".jpg");
    ImageIO.write(img, "jpg", outputFile);
}

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

Далее обратите внимание, что рендеринг существующего GlyphVector через drawGlyphVector был задокументирован как «самый быстрый способ рендеринга набора символов на экране», поэтому, если нам все равно нужно выполнить эту подготовительную работу для вычисления границ, почему бы не использовать ее для рендеринг…

Другие вопросы по теме