Поведение рендеринга пути JavaFX Canvas appendSVG

Я хочу нарисовать вектор по частям, перерисовывая вектор каждый раз, когда добавляется новая часть. У меня есть строка, содержащая каждую часть пути для финального svg, который зацикливается (обозначается знаком «;» в строке). Кроме того, я добавил strokeLine в качестве своего рода индикатора выполнения, чтобы отслеживать, какая часть вектора нарисована.

public void renderObject(GraphicsContext playGraphics, Canvas toUpdate)
{       
    playGraphics.beginPath();
    String toAppend = "M 215 256; L 215 256; L 215 256; L 215 256; L 225 241; L 234 231; L 246 223; L 266 214; L 284 208; L 309 204; L 340 200; L 378 199; L 416 199; L 444 199; L 473 203; L 485 206; L 496 211; L 506 218; L 510 224; L 513 233; L 515 243; L 516 257; L 512 270; L 502 285; L 493 298; L 483 308; L 476 315; L 472 318; L 469 320; L 468 320; L 468 320; L 468 320; L 468 317; L 472 309; L 480 300; L 492 293; L 510 287; L 535 283; L 557 282; L 580 283; L 593 287; L 607 295; L 623 311; L 634 333; L 640 355; L 642 396; L 639 430; L 624 467; L 602 508; L 582 536; L 557 563; L 524 585; L 490 602; L 464 611; L 432 619; L 420 621; L 404 622; L 393 622; L 383 621; L 376 620; L 372 618; L 365 610; L 360 598; L 358 578; L 357 554; L 361 514; L 371 493; L 386 463; L 412 422; L 432 395; L 456 362; L 480 329; L 506 299; L 533 271; L 560 247; L 600 213; L 620 194; L 626 189; L 629 184; L 630 182; L 632 178"; 
    for (int i=0; i < toAppend.split(";").length; i++)
    {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        playGraphics.clearRect(0, 0, toUpdate.getWidth(), toUpdate.getHeight());
        playGraphics.strokeLine(50, 50, 50+10*i, 50);

        playGraphics.appendSVGPath(toAppend.split(";")[i]);
        playGraphics.stroke();
    }
}

Однако после реализации на живом холсте вектор не рисуется, и отображается только индикатор выполнения. Это довольно странно, учитывая, что после завершения рендеринга векторное изображение может быть полностью отображено на холсте, если объект graphicsContext используется снова и вызывается другой playGraphics.stroke();.

Почему playGraphics.stroke(); не запускается, а playGraphics.strokeLine работает нормально?

Поведение рендеринга пути JavaFX Canvas appendSVG Индикатор выполнения, показывающий, что рендеринг происходит, но вектор не отображается Поведение рендеринга пути JavaFX Canvas appendSVG Индикатор выполнения, показывающий, что рендеринг происходит, но вектор не отображается Поведение рендеринга пути JavaFX Canvas appendSVG Вектор, наконец, отображается, но только после того, как точка была нарисована на холсте, повторно вызывая playGraphics.stroke();

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

Ответы 1

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

Похоже, пути SVG каким-то образом удаляются из текущего пути, когда вы вызываете stroke() (после чего они не могут быть добавлены к текущему пути). Если бы вы вызывали stroke() только после завершения цикла (что, как я понимаю, подорвало бы эффект анимации и, следовательно, не является решением), ваш рисунок выглядел бы так, как ожидалось.

Это осложняется тем фактом, что ваши спящие вызовы игнорируют правила многопоточности JavaFX. Ваш метод renderObject вызывается в потоке приложения JavaFX?

  • Если да, то вызовы sleep препятствуют обработке событий, в том числе рисованию.
  • В противном случае вызовы метода GraphicsContext являются недопустимыми и могут привести к сбою или непредсказуемому поведению. Из Документация GraphicsContext:

… Once a Canvas node is attached to a scene, it must be modified on the JavaFX Application Thread.

Calling any method on the GraphicsContext is considered modifying its corresponding Canvas and is subject to the same threading rules.

Как только ваш Canvas отображается, вы должны сделать эти вызовы GraphicsContext в потоке приложения. Но вы не можете спать в потоке приложения; это приводит к зависанию всех событий графического интерфейса, включая все визуальные обновления и все ответы на ввод пользователя.

Есть несколько способов правильно реализовать многопоточность. Проще всего создать один новый поток, в котором разрешены вызовы sleep, при этом убедитесь, что ваши вызовы Canvas GraphicsContext происходят в потоке приложения JavaFX с использованием Platform.runLater.

Некоторые другие примечания:

  • Регулярные выражения стоят дорого. toAppend.split(";") это не переменная, это дорогой операция. И вы повторяете эту операцию дважды в каждой итерации цикла! Вы должны вызвать split(";")однажды до начала цикла и сохранить возвращаемый массив в переменной. (Возможно, JVM сможет выполнить эту оптимизацию во время выполнения, но это не гарантируется.)
  • Если на то пошло, вам вообще не нужно split; вы можете поместить все команды SVG в массив, который имеет дополнительное преимущество, заключающееся в том, что его легче читать.
  • Прерывания не происходят случайно. Если ваш поток прерван, это означает, что кто-то явно просит вас выполнить прекрати то, что ты делаешь и выйти корректно. Игнорирование прерывания означает, что ваш поток является мошенническим потоком, который нельзя остановить. В большинстве случаев лучше всего поместить все тело метода в try/catch, чтобы он автоматически завершился, если его прервали.

Итак, имея в виду все вышеперечисленное, вы хотите что-то вроде этого:

public void renderObject(GraphicsContext playGraphics, Canvas toUpdate)
{       
    if (!Platform.isFxApplicationThread())
    {
        throw new IllegalStateException(
            "Must be called in JavaFX application thread");
    }

    String[] toAppend = {
        "M 215 256", "L 215 256", "L 215 256", "L 215 256",
        "L 225 241", "L 234 231", "L 246 223", "L 266 214",
        "L 284 208", "L 309 204", "L 340 200", "L 378 199",
        "L 416 199", "L 444 199", "L 473 203", "L 485 206",
        "L 496 211", "L 506 218", "L 510 224", "L 513 233",
        "L 515 243", "L 516 257", "L 512 270", "L 502 285",
        "L 493 298", "L 483 308", "L 476 315", "L 472 318",
        "L 469 320", "L 468 320", "L 468 320", "L 468 320",
        "L 468 317", "L 472 309", "L 480 300", "L 492 293",
        "L 510 287", "L 535 283", "L 557 282", "L 580 283",
        "L 593 287", "L 607 295", "L 623 311", "L 634 333",
        "L 640 355", "L 642 396", "L 639 430", "L 624 467",
        "L 602 508", "L 582 536", "L 557 563", "L 524 585",
        "L 490 602", "L 464 611", "L 432 619", "L 420 621",
        "L 404 622", "L 393 622", "L 383 621", "L 376 620",
        "L 372 618", "L 365 610", "L 360 598", "L 358 578",
        "L 357 554", "L 361 514", "L 371 493", "L 386 463",
        "L 412 422", "L 432 395", "L 456 362", "L 480 329",
        "L 506 299", "L 533 271", "L 560 247", "L 600 213",
        "L 620 194", "L 626 189", "L 629 184", "L 630 182",
        "L 632 178"
    };

    // If the SVG string is not hard-coded, create the array here:
    // String[] toAppend = svgString.split(";");

    Runnable pathBuilder = () -> {
        try
        {
            StringBuilder path = new StringBuilder();

            for (String segment : toAppend)
            {
                Thread.sleep(100);

                path.append(" ").append(segment);
                String pathToDraw = path.toString();

                Platform.runLater(() -> {
                    playGraphics.beginPath();
                    playGraphics.appendSVGPath(pathToDraw);
                    playGraphics.stroke();
                });
            }
        }
        catch (InterruptedException e)
        {
            // Someone wants us to exit, so fall through and return.
            e.printStackTrace();
        }
    };

    Thread pathBuildingThread = new Thread(pathBuilder);
    pathBuildingThread.setDaemon(true);
    pathBuildingThread.start();
}

Вы правы в том, что проблема заключается в вызове beginStroke один раз, а не в каждой итерации. Я считаю, что это ошибка JavaFx, потому что она отлично работает, если вы используете формальные методы moveTo и lineTo.

Zachery Utt 17.06.2019 00:25

Но решение, которое мне нужно, основано на перерисовке всего объекта по 1 пути.

Zachery Utt 17.06.2019 00:26

Я согласен, что это похоже на ошибку. На мой взгляд, gc.appendSVGPath("M 100 100 L 200 200"); должен функционировать идентично gc.moveTo(100, 100); gc.lineTo(200, 200);.

VGR 17.06.2019 16:53

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