Можно ли напрямую использовать встроенные во Flutter методы рисования Canvas для визуализации штрихов переменной ширины?

Могут ли встроенные во Flutter методы рисования Canvas напрямую использоваться для рендеринга штрихов переменной ширины, например, для отражения давления, прилагаемого к каждому штриху в приложении для рукописного ввода?

В идеале способом, совместимым с сохранением в формате SVG в стиле XML (пример внизу).


Что я заметил / проблемы, которые у меня возникают / текущие попытки:

canvas.drawPath, canvas.drawPoints, canvas.drawPolygon, canvas.drawLines и т. д. все принимают только один объект Paint, который, в свою очередь, может иметь один strokeWidth (в отличие от списков объектов Paint или strokeWidths, чтобы параметры пути, помимо положения, могли изменяться от точки к точке и интерполироваться между ними).

Рисование линий, многоугольников или различных точек strokeWidths или радиусы путем перебора списков данных положения и давления и использования соответствующего метода Canvas не приводит к интерполяции / пути не выглядят непрерывно обведенными.

Снимок экрана из OneNote, показывающий желаемое поведение:

Снимок экрана из приложения, который дает минимальный рабочий пример ниже:

(Неоптимизированный) минимальный рабочий пример:

import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';

void main() {
  runApp(Container(
    color: Colors.white,
    child: Writeable(),
  ));
}

class Writeable extends StatefulWidget {
  @override
  _WriteableState createState() => _WriteableState();
}

class _WriteableState extends State<Writeable> {
  List<List<double>> pressures = List<List<double>>();
  List<Offset> currentLine = List<Offset>();
  List<List<Offset>> lines = List<List<Offset>>();
  List<double> currentLinePressures = List<double>();
  double pressure;
  Offset position;
  Color color = Colors.black;
  Painter painter;
  CustomPaint paintCanvas;

  @override
  Widget build(BuildContext context) {
    painter = Painter(
        lines: lines,
        currentLine: currentLine,
        pressures: pressures,
        currentLinePressures: currentLinePressures,
        color: color);

    paintCanvas = CustomPaint(
      painter: painter,
    );

    return Listener(
      onPointerMove: (details) {
        setState(() {
          currentLinePressures.add(details.pressure);
          currentLine.add(details.localPosition);
        });
      },
      onPointerUp: (details) {
        setState(() {
          lines.add(currentLine.toList());
          pressures.add(currentLinePressures.toList());
          currentLine.clear();
          currentLinePressures.clear();
        });
      },
      child: paintCanvas,
    );
  }
}

class Painter extends CustomPainter {
  Painter(
      {@required this.lines,
      @required this.currentLine,
      @required this.color,
      @required this.pressures,
      @required this.currentLinePressures});

  final List<List<Offset>> lines;
  final List<Offset> currentLine;
  final Color color;
  final List<List<double>> pressures;
  final List<double> currentLinePressures;

  double scalePressures = 10;
  Paint paintStyle = Paint();

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return true;
  }

  // Paints here using drawPoints and PointMode.lines, but have also tried
  // PointMode.points, PointMode.polygon and drawPath with a path variable and
  // moveTo, lineTo methods.
  @override
  void paint(Canvas canvas, Size size) {
    // Paint line currently being drawn (points added since pointer was
    // last lifted)
    for (int i = 0; i < currentLine.length - 1; i++) {
      paintStyle.strokeWidth = currentLinePressures[i] * scalePressures;
      canvas.drawPoints(
          PointMode.lines, [currentLine[i], currentLine[i + 1]], paintStyle);
    }
    // Paint all completed lines drawn since app start
    for (int i = 0; i < lines.length; i++) {
      for (int j = 0; j < lines[i].length - 1; j++) {
        paintStyle.strokeWidth = pressures[i][j] * scalePressures;
        canvas.drawPoints(
            PointMode.lines, [lines[i][j], lines[i][j + 1]], paintStyle);
      }
    }
  }
}

Я собираюсь попробовать написать свою собственную реализацию для рендеринга эстетичных SVG-дружественных данных из PointerEvents, но многие из существующих классов чувствуют себя совместимыми с SVG/pretty-vectors (например, все lineTos, moveTos, типы и окончания штрихов и другие параметры), что я подумал, что стоит проверить, есть ли что-то, что я пропустил, и эти методы уже могут это сделать?


Пример нескольких строк в файле SVG, сохраненном Xournal++, с изменением параметра ширины штриха для каждого сегмента линии, и все другие перечисленные параметры предположительно также могут измениться. Каждая строка содержит команду moveTo (M) и команду lineTo (L), где последняя рисует линию из текущей позиции (последний moveTo-ed или lineTo-ed), напоминающую сегменты / подпути Flutter и текущую точку., до указанного смещения:

<path style="fill:none;stroke-width:0.288794;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,100%,0%);stroke-opacity:1;comp-op:src;clip-to-self:true;stroke-miterlimit:10;" d="M 242.683594 45.519531 L 242.980469 45.476562 "/>
<path style="fill:none;stroke-width:0.295785;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,100%,0%);stroke-opacity:1;comp-op:src;clip-to-self:true;stroke-miterlimit:10;" d="M 242.980469 45.476562 L 243.28125 45.308594 "/>
<path style="fill:none;stroke-width:0.309105;stroke-linecap:round;stroke-linejoin:round;stroke:rgb(0%,100%,0%);stroke-opacity:1;comp-op:src;clip-to-self:true;stroke-miterlimit:10;" d="M 243.28125 45.308594 L 243.601562 45.15625 "/>

Кажется, что подход состоит в том, чтобы "нарисовать очень короткую линию, изменить ширину обводки, нарисовать следующую очень короткую линию, начиная с предыдущей позиции", что я пытался имитировать с помощью описанного выше метода рисования.

0 ответов

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